aboutsummaryrefslogtreecommitdiffstats
path: root/src/qmlcompiler
diff options
context:
space:
mode:
Diffstat (limited to 'src/qmlcompiler')
-rw-r--r--src/qmlcompiler/CMakeLists.txt52
-rw-r--r--src/qmlcompiler/doc/qtqmlcompiler-index.qdoc81
-rw-r--r--src/qmlcompiler/doc/qtqmlcompiler.qdocconf49
-rw-r--r--src/qmlcompiler/doc/src/qtqmlcompiler-module.qdoc13
-rw-r--r--src/qmlcompiler/doc/src/tutorial.qdoc229
-rw-r--r--src/qmlcompiler/qcoloroutput.cpp50
-rw-r--r--src/qmlcompiler/qcoloroutput_p.h4
-rw-r--r--src/qmlcompiler/qdeferredpointer_p.h46
-rw-r--r--src/qmlcompiler/qqmljsbasicblocks.cpp606
-rw-r--r--src/qmlcompiler/qqmljsbasicblocks_p.h59
-rw-r--r--src/qmlcompiler/qqmljscodegenerator.cpp2626
-rw-r--r--src/qmlcompiler/qqmljscodegenerator_p.h160
-rw-r--r--src/qmlcompiler/qqmljscompilepass_p.h151
-rw-r--r--src/qmlcompiler/qqmljscompiler.cpp141
-rw-r--r--src/qmlcompiler/qqmljscompiler_p.h44
-rw-r--r--src/qmlcompiler/qqmljscompilerstats.cpp188
-rw-r--r--src/qmlcompiler/qqmljscompilerstats_p.h92
-rw-r--r--src/qmlcompiler/qqmljscompilerstatsreporter.cpp118
-rw-r--r--src/qmlcompiler/qqmljscompilerstatsreporter_p.h57
-rw-r--r--src/qmlcompiler/qqmljscontextualtypes_p.h113
-rw-r--r--src/qmlcompiler/qqmljsfunctioninitializer.cpp102
-rw-r--r--src/qmlcompiler/qqmljsfunctioninitializer_p.h2
-rw-r--r--src/qmlcompiler/qqmljsimporter.cpp210
-rw-r--r--src/qmlcompiler/qqmljsimporter_p.h94
-rw-r--r--src/qmlcompiler/qqmljsimportvisitor.cpp655
-rw-r--r--src/qmlcompiler/qqmljsimportvisitor_p.h37
-rw-r--r--src/qmlcompiler/qqmljslinter.cpp157
-rw-r--r--src/qmlcompiler/qqmljslinter_p.h17
-rw-r--r--src/qmlcompiler/qqmljslintercodegen.cpp29
-rw-r--r--src/qmlcompiler/qqmljslintercodegen_p.h4
-rw-r--r--src/qmlcompiler/qqmljsliteralbindingcheck.cpp143
-rw-r--r--src/qmlcompiler/qqmljsliteralbindingcheck_p.h35
-rw-r--r--src/qmlcompiler/qqmljsloadergenerator.cpp2
-rw-r--r--src/qmlcompiler/qqmljsloadergenerator_p.h6
-rw-r--r--src/qmlcompiler/qqmljslogger.cpp419
-rw-r--r--src/qmlcompiler/qqmljslogger_p.h182
-rw-r--r--src/qmlcompiler/qqmljsloggingutils.cpp136
-rw-r--r--src/qmlcompiler/qqmljsloggingutils.h82
-rw-r--r--src/qmlcompiler/qqmljsloggingutils_p.h108
-rw-r--r--src/qmlcompiler/qqmljsmetatypes.cpp46
-rw-r--r--src/qmlcompiler/qqmljsmetatypes_p.h201
-rw-r--r--src/qmlcompiler/qqmljsoptimizations.cpp503
-rw-r--r--src/qmlcompiler/qqmljsoptimizations_p.h65
-rw-r--r--src/qmlcompiler/qqmljsregistercontent.cpp160
-rw-r--r--src/qmlcompiler/qqmljsregistercontent_p.h148
-rw-r--r--src/qmlcompiler/qqmljsresourcefilemapper_p.h4
-rw-r--r--src/qmlcompiler/qqmljsscope.cpp513
-rw-r--r--src/qmlcompiler/qqmljsscope_p.h724
-rw-r--r--src/qmlcompiler/qqmljsscopesbyid_p.h30
-rw-r--r--src/qmlcompiler/qqmljsshadowcheck.cpp167
-rw-r--r--src/qmlcompiler/qqmljsshadowcheck_p.h33
-rw-r--r--src/qmlcompiler/qqmljsstoragegeneralizer.cpp20
-rw-r--r--src/qmlcompiler/qqmljsstoragegeneralizer_p.h10
-rw-r--r--src/qmlcompiler/qqmljsstorageinitializer.cpp81
-rw-r--r--src/qmlcompiler/qqmljsstorageinitializer_p.h40
-rw-r--r--src/qmlcompiler/qqmljstypedescriptionreader.cpp85
-rw-r--r--src/qmlcompiler/qqmljstypedescriptionreader_p.h6
-rw-r--r--src/qmlcompiler/qqmljstypepropagator.cpp1341
-rw-r--r--src/qmlcompiler/qqmljstypepropagator_p.h36
-rw-r--r--src/qmlcompiler/qqmljstypereader.cpp16
-rw-r--r--src/qmlcompiler/qqmljstyperesolver.cpp1200
-rw-r--r--src/qmlcompiler/qqmljstyperesolver_p.h153
-rw-r--r--src/qmlcompiler/qqmljsutils.cpp60
-rw-r--r--src/qmlcompiler/qqmljsutils_p.h86
-rw-r--r--src/qmlcompiler/qqmljsvaluetypefromstringcheck.cpp63
-rw-r--r--src/qmlcompiler/qqmljsvaluetypefromstringcheck_p.h48
-rw-r--r--src/qmlcompiler/qqmlsa.cpp1617
-rw-r--r--src/qmlcompiler/qqmlsa.h435
-rw-r--r--src/qmlcompiler/qqmlsa_p.h251
-rw-r--r--src/qmlcompiler/qqmlsaconstants.h50
-rw-r--r--src/qmlcompiler/qqmlsasourcelocation.cpp125
-rw-r--r--src/qmlcompiler/qqmlsasourcelocation.h83
-rw-r--r--src/qmlcompiler/qqmlsasourcelocation_p.h53
-rw-r--r--src/qmlcompiler/qresourcerelocater.cpp1
-rw-r--r--src/qmlcompiler/qresourcerelocater_p.h4
75 files changed, 11636 insertions, 4121 deletions
diff --git a/src/qmlcompiler/CMakeLists.txt b/src/qmlcompiler/CMakeLists.txt
index b3cd96e1d7..00470cebc1 100644
--- a/src/qmlcompiler/CMakeLists.txt
+++ b/src/qmlcompiler/CMakeLists.txt
@@ -5,52 +5,70 @@
## QmlCompilerPrivate Module:
#####################################################################
-qt_internal_add_module(QmlCompilerPrivate
- INTERNAL_MODULE
- GENERATE_CPP_EXPORTS
- GENERATE_PRIVATE_CPP_EXPORTS
- PLUGIN_TYPES qmllint
+qt_internal_add_module(QmlCompiler
+ PLUGIN_TYPES qmllint
SOURCES
- qcoloroutput_p.h qcoloroutput.cpp
+ qcoloroutput.cpp qcoloroutput_p.h
qdeferredpointer_p.h
qqmljsannotation.cpp qqmljsannotation_p.h
qqmljsbasicblocks.cpp qqmljsbasicblocks_p.h
qqmljscodegenerator.cpp qqmljscodegenerator_p.h
qqmljscompilepass_p.h
qqmljscompiler.cpp qqmljscompiler_p.h
+ qqmljscompilerstats.cpp qqmljscompilerstats_p.h
+ qqmljscompilerstatsreporter.cpp qqmljscompilerstatsreporter_p.h
+ qqmljscontextualtypes_p.h
qqmljsfunctioninitializer.cpp qqmljsfunctioninitializer_p.h
qqmljsimporter.cpp qqmljsimporter_p.h
qqmljsimportvisitor.cpp qqmljsimportvisitor_p.h
+ qqmljslinter.cpp qqmljslinter_p.h
+ qqmljslintercodegen.cpp qqmljslintercodegen_p.h
qqmljsliteralbindingcheck.cpp qqmljsliteralbindingcheck_p.h
qqmljsloadergenerator.cpp qqmljsloadergenerator_p.h
- qqmljslogger_p.h qqmljslogger.cpp
- qqmljsmetatypes_p.h qqmljsmetatypes.cpp
+ qqmljslogger.cpp qqmljslogger_p.h
+ qqmljsloggingutils.cpp qqmljsloggingutils.h qqmljsloggingutils_p.h
+ qqmljsmetatypes.cpp qqmljsmetatypes_p.h
+ qqmljsoptimizations.cpp qqmljsoptimizations_p.h
qqmljsregistercontent.cpp qqmljsregistercontent_p.h
qqmljsresourcefilemapper.cpp qqmljsresourcefilemapper_p.h
qqmljsscope.cpp qqmljsscope_p.h
qqmljsscopesbyid_p.h
qqmljsshadowcheck.cpp qqmljsshadowcheck_p.h
qqmljsstoragegeneralizer.cpp qqmljsstoragegeneralizer_p.h
+ qqmljsstorageinitializer.cpp qqmljsstorageinitializer_p.h
qqmljstypedescriptionreader.cpp qqmljstypedescriptionreader_p.h
qqmljstypepropagator.cpp qqmljstypepropagator_p.h
qqmljstypereader.cpp qqmljstypereader_p.h
qqmljstyperesolver.cpp qqmljstyperesolver_p.h
+ qqmljsutils.cpp qqmljsutils_p.h
+ qqmljsvaluetypefromstringcheck.cpp qqmljsvaluetypefromstringcheck_p.h
+ qqmlsa.cpp qqmlsa.h qqmlsa_p.h
+ qqmlsaconstants.h
+ qqmlsasourcelocation.cpp qqmlsasourcelocation.h qqmlsasourcelocation_p.h
qresourcerelocater.cpp qresourcerelocater_p.h
- qqmljsutils_p.h qqmljsutils.cpp
- qqmljslinter_p.h qqmljslinter.cpp
- qqmljslintercodegen_p.h qqmljslintercodegen.cpp
- qqmlsa_p.h qqmlsa.cpp
+ NO_UNITY_BUILD_SOURCES
+ qqmljsoptimizations.cpp
PUBLIC_LIBRARIES
- Qt::CorePrivate
+ Qt::Core
+ Qt::Qml
+ LIBRARIES
Qt::QmlPrivate
+ PRIVATE_MODULE_INTERFACE
+ Qt::QmlPrivate
+
)
-qt_internal_add_resource(QmlCompilerPrivate "builtins"
+qt_path_join(qml_build_dir "${QT_BUILD_DIR}" "${INSTALL_QMLDIR}")
+qt_internal_add_resource(QmlCompiler "builtins"
PREFIX
"/qt-project.org/qml/builtins"
BASE
- "${CMAKE_CURRENT_SOURCE_DIR}/../imports/builtins/"
+ "${qml_build_dir}"
FILES
- "${CMAKE_CURRENT_SOURCE_DIR}/../imports/builtins/builtins.qmltypes"
- "${CMAKE_CURRENT_SOURCE_DIR}/../imports/builtins/jsroot.qmltypes"
+ "${qml_build_dir}/builtins.qmltypes"
+ "${qml_build_dir}/jsroot.qmltypes"
+)
+
+qt_internal_add_docs(QmlCompiler
+ doc/qtqmlcompiler.qdocconf
)
diff --git a/src/qmlcompiler/doc/qtqmlcompiler-index.qdoc b/src/qmlcompiler/doc/qtqmlcompiler-index.qdoc
new file mode 100644
index 0000000000..3de06d577e
--- /dev/null
+++ b/src/qmlcompiler/doc/qtqmlcompiler-index.qdoc
@@ -0,0 +1,81 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \page qtqmlcompiler-index.html
+ \title Qt QML Compiler
+ \brief Provides tools for static analysis of QML code.
+
+ The Qt QML Compiler module contains shared functionality needed by QML
+ tooling like the \l{Qt Quick Compiler} and \l{qmllint Reference}{qmllint}.
+ It also provides the QQmlSA framework, which can be used to extend the
+ built-in analysis capabilities of the tools.
+
+ \section1 Using the Module
+
+ \include {module-use.qdocinc} {using the c++ api}
+
+ \section2 Building with CMake
+
+ \include {module-use.qdocinc} {building with cmake} {QmlCompiler}
+
+ \section2 Building with qmake
+
+ \include {module-use.qdocinc} {building_with_qmake} {QmlCompiler}
+
+ \section1 Using the QQmlSA framework
+
+ The Qt QML Compiler module offers the QQmlSA framework which provides tools
+ for static analysis of QML code. These tools can help ensure syntactic
+ validity and warn about QML anti-patterns.
+ Adding static analysis to a QML program is done by writing plugins. They
+ will run a collection of analysis passes over the elements and properties
+ of the QML code. The passes can be registered with a PassManager which
+ holds the passes and can be called to analyze an element and its children.
+ A pass is a check for a certain rule or condition evaluated on elements or
+ properties. If the condition is met, the pass can warn the user of an
+ indentified issue in the code and maybe even suggest a fix. It is called a
+ pass because the analysis performed on elements and properties happens by
+ running a collection of passes on them in succesion. Each pass should be
+ responsible for identifying one specific issue only. Combining a set of
+ passes can perform more complex analysis and, together, form a plugin.
+ Element passes are defined by two main components, namely \c shouldRun()
+ and \c run(). When performing the analysis, the pass manager will execute
+ the pass over every element it encounters while traversing the children of
+ the root element. For each element, if \c shouldRun() evaluated on that
+ element returns \c true then \c run() is executed on it.
+ Passes on properties trigger on three different events, namely when the
+ property is bound, when it is read, and when it is written to. These can be
+ implemented by overriding the \c onBinding(), \c onRead() and \c onWrite()
+ functions respectively.
+ As the code grows, so does the number of elements and properties.
+ Performing the static analysis passes on all of them can become expensive.
+ That's why it is good to be granular when deciding which elements and
+ properties to analyze. For elements, the \c shouldRun() is intended to be a
+ cheap check to determine if \c run(), which performs the real computation,
+ should be run. For properties, the selection is done when registering the
+ passes with the manager. The \c registerPropertyPass() function takes the
+ \c moduleName, \c typeName and \c propertyName strings as arguments. These
+ are used to filter down the set of properties affected by the registered
+ pass.
+
+
+ \section1 Examples
+
+ The \l{QML Static Analysis Tutorial} shows how to use the \c{QQmlSA}
+ framework to create a custom \l{qmllint Reference}{qmllint} pass.
+
+ \section1 Reference
+
+ \list
+ \li \l {Qt QML Compiler C++ Classes}
+ - the C++ API provided by the QmlCompiler module
+ \li QML tooling using the static analysis capabilities
+ \list
+ \li \l{QML script compiler}
+ \li \l{qmllint Reference}{qmllint}
+ \li \l{\QMLLS Reference}{\QMLLS}
+ \li \l{QML type compiler}
+ \endlist
+ \endlist
+ */
diff --git a/src/qmlcompiler/doc/qtqmlcompiler.qdocconf b/src/qmlcompiler/doc/qtqmlcompiler.qdocconf
new file mode 100644
index 0000000000..d5b45c2731
--- /dev/null
+++ b/src/qmlcompiler/doc/qtqmlcompiler.qdocconf
@@ -0,0 +1,49 @@
+include($QT_INSTALL_DOCS/global/qt-module-defaults.qdocconf)
+include($QT_INSTALL_DOCS/config/exampleurl-qtdeclarative.qdocconf)
+
+project = QtQmlCompiler
+description = Qt QML Compiler Reference Documentation
+version = $QT_VERSION
+
+qhp.projects = QtQmlCompiler
+
+qhp.QtQmlCompiler.file = qtqmlcompiler.qhp
+qhp.QtQmlCompiler.namespace = org.qt-project.qtqmlcompiler.$QT_VERSION_TAG
+qhp.QtQmlCompiler.virtualFolder = qtqmlcompiler
+qhp.QtQmlCompiler.indexTitle = Qt QML Compiler
+qhp.QtQmlCompiler.indexRoot =
+
+qhp.QtQmlCompiler.subprojects = classes
+qhp.QtQmlCompiler.subprojects.classes.title = Classes
+qhp.QtQmlCompiler.subprojects.classes.indexTitle = Qt QML Compiler C++ Classes
+qhp.QtQmlCompiler.subprojects.classes.selectors = namespace class
+qhp.QtQmlCompiler.subprojects.classes.sortPages = true
+
+tagfile = qtqmlcompiler.tags
+
+depends += \
+ qtcore \
+ qtnetwork \
+ qtqmlmodels \
+ qtqmlworkerscript \
+ qtgui \
+ qtquick \
+ qtdoc \
+ qtlinguist \
+ qtwidgets \
+ qtquickcontrols \
+ qmake \
+ qtcmake \
+ qtqml
+
+headerdirs += ..
+
+sourcedirs += ..
+
+navigation.landingpage = "Qt QML Compiler"
+navigation.cppclassespage = "Qt QML Compiler C++ Classes"
+
+# Enforce zero documentation warnings
+warninglimit = 0
+exampledirs += \
+ ../../../examples/qmlcompiler
diff --git a/src/qmlcompiler/doc/src/qtqmlcompiler-module.qdoc b/src/qmlcompiler/doc/src/qtqmlcompiler-module.qdoc
new file mode 100644
index 0000000000..152fc94cd4
--- /dev/null
+++ b/src/qmlcompiler/doc/src/qtqmlcompiler-module.qdoc
@@ -0,0 +1,13 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \module QtQmlCompiler
+ \modulestate Technical Preview
+ \title Qt QML Compiler C++ Classes
+ \qtcmakepackage QmlCompiler
+
+ For more information on the Qt QmlCompiler module, see the
+ \l{Qt QML Compiler} module documentation.\brief The Qt QML Compiler module provides tools
+ for static analysis of QML code.
+*/
diff --git a/src/qmlcompiler/doc/src/tutorial.qdoc b/src/qmlcompiler/doc/src/tutorial.qdoc
new file mode 100644
index 0000000000..ef96ee1e2c
--- /dev/null
+++ b/src/qmlcompiler/doc/src/tutorial.qdoc
@@ -0,0 +1,229 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+\page qqmlsa-tutorial.html
+\title QML Static Analysis Tutorial
+\brief An introduction on writing your own qmllint checks
+\nextpage QML Static Analysis 1 - Basic Setup
+
+This tutorial gives an introduction to QQmlSA, the API to statically analyze QML code.
+
+Through the different steps of this tutorial we will learn about how to set up a basic qmllint
+plugin, we will create our own custom analysis pass, and we will add support for fixit-hints.
+
+Chapter one starts with a minimal plugin and the following chapters introduce new concepts.
+
+The tutorial's source code is located in the \c{examples/qmlcompiler/tutorials/helloworld} directory.
+
+Tutorial chapters:
+
+\list 1
+\li \l {QML Static Analysis 1 - Basic Setup}{Basic Setup}
+\li \l {QML Static Analysis 2 - Custom Pass}{Custom Pass}
+\li \l {QML Static Analysis 3 - Fixit Hints}{Fixit Hints}
+\endlist
+
+*/
+
+/*!
+\page qqmlsa-tutorial1.html
+\title QML Static Analysis 1 - Basic Setup
+\previouspage QML Static Analysis Tutorial
+\nextpage QML Static Analysis 2 - Custom Pass
+
+This chapter introduces the basic structure of a qmllint extension plugin,
+and how it can be used with qmllint.
+
+To create our plugin, we first need to make the QmlCompiler module available:
+\quotefromfile tutorials/helloworld/chapter1/CMakeLists.txt
+\skipto find_package
+\printline find_package
+
+We then create a plugin, and link it against the QmlCompiler module.
+
+\skipto qt_add_plugin(HelloWorldPlugin)
+\printuntil
+
+The implementation follows the pattern for \l{The High-Level API: Writing Qt Extensions}{extending
+Qt with a plugin}: We subclass the \l{QQmlSA::LintPlugin},
+\quotefromfile tutorials/helloworld/chapter1/helloplugin.h
+\skipto class HelloWorldPlugin
+\printuntil };
+
+The plugin references a \c{plugin.json} file, which contains some important information:
+\quotefile tutorials/helloworld/chapter1/plugin.json
+\c{name}, \c{author}, \c{version} and \c{description} are meta-data to describe the plugin.
+
+Each plugin can have one or more logging categories, which are used to thematically group
+warnings.
+\c{loggingCategories} takes an array of categories, each consisting of
+\list
+ \li \c{name}, which is used to identify the category, and as the flag name in qmllint,
+ \li \c{settingsName}, which is used to configure the category in qmllint.ini
+ \li \c{description}, which should describe what kind of warning messages are tagged with the category
+\endlist
+
+In our example, we have only one logging category, \c{hello-world}. For each category, we have to
+create a \l{QQmlSA::}{LoggerWarningId} object in our plugin.
+
+\quotefromfile tutorials/helloworld/chapter1/helloplugin.cpp
+\skipto static constexpr QQmlSA::LoggerWarningId
+\printline static constexpr QQmlSA::LoggerWarningId
+
+We construct it with a string literal which should have the format
+\c{Plugin.<plugin name>.<category name>}.
+
+Lastly, for our plugin to actually do anything useful, we have to implement the
+\c{registerPasses} function.
+
+\skipto void HelloWorldPlugin::registerPasses(
+\printuntil }
+
+We check whether our category is enabled, and print a diagnostic indicating its status
+via \l{qDebug}.
+Note that the plugin currently does not do anything useful, as we do not register any
+passes. This will done in the next part of this tutorial.
+
+We can however already verify that our plugin is correctly detected. We use the following command to
+check it:
+
+\badcode
+qmllint -P /path/to/the/directory/containing/the/plugin --Plugin.HelloWorld.hello-world info test.qml
+\endcode
+
+The \c{-P} options tells qmllint to look for plugins in the specified folder. To avoid conflicts
+with Qt internal categories, plugin categories are always prefixed with "Plugin", then followed by
+a dot, the plugin name, another dot and finally the category.
+Running the command should print \c{Hello World Plugin is enabled}.
+
+*/
+
+
+/*!
+\page qqmlsa-tutorial2.html
+\title QML Static Analysis 2 - Custom Pass
+\previouspage QML Static Analysis 1 - Basic Setup
+\nextpage QML Static Analysis 3 - Fixit Hints
+
+This chapter shows how custom analysis passes can be added to \l{qmllint Reference}{qmllint},
+by extending the plugin we've created in the last chapter.
+For demonstration purposes, we will create a plugin which checks whether
+\l{Text} elements have "Hello world!" assigned to their text property.
+
+To do this, we create a new class derived from
+\l{QQmlSA::ElementPass}{ElementPass}.
+
+\note There are two types of passes that
+plugins can register, \l{QQmlSA::ElementPass}{ElementPasses}, and \l{QQmlSA::PropertyPass}{PropertyPasses}.
+In this tutorial, we will only consider the simpler \c{ElementPass}.
+
+\quotefromfile tutorials/helloworld/chapter2/helloplugin.cpp
+\skipto class HelloWorldElementPass : public QQmlSA::ElementPass
+\printuntil };
+
+As our \c{HelloWorldElementPass} should analyze \c{Text} elements,
+we need a reference to the \c{Text} type. We can use the
+\l{QQmlSA::GenericPass::resolveType}{resolveType} function to obtain it.
+As we don't want to constantly re-resolve the type, we do this
+once in the constructor, and store the type in a member variable.
+
+\skipto HelloWorldElementPass::HelloWorldElementPass(
+\printuntil }
+
+The actual logic of our pass happens in two functions:
+\l{QQmlSA::ElementPass::shouldRun}{shouldRun} and
+\l{QQmlSA::ElementPass::run}{run}. They will run on
+all Elements in the file that gets analyzed by qmllint.
+
+In our \c{shouldRun} method, we check whether the current
+Element is derived from Text, and check whether it has
+a binding on the text property.
+\skipto bool HelloWorldElementPass::shouldRun
+\printuntil }
+
+Only elements passing the checks there will then be analyzed by our
+pass via its \c{run} method. It would be possible to do all checking
+inside of \c{run} itself, but it is generally preferable to have
+a separation of concerns – both for performance and to enhance
+code readability.
+
+In our \c{run} function, we retrieve the bindings to the text
+property. If the bound value is a string literal, we check
+if it's the greeting we expect.
+
+\skipto void HelloWorldElementPass::run
+\printuntil /^}/
+
+\note Most of the time, a property will only have one
+binding assigned to it. However, there might be for
+instance a literal binding and a \l{Behavior} assigned
+to the same property.
+
+Lastly, we need to create an instance of our pass, and
+register it with the \l{QQmlSA::PassManager}{PassManager}. This is done by
+adding
+\skipto manager->registerElementPass
+\printline manager->registerElementPass
+to the \c{registerPasses} functions of our plugin.
+
+We can test our plugin by invoking \l{qmllint Reference}{qmllint} on an example
+file via
+\badcode
+qmllint -P /path/to/the/directory/containing/the/plugin --Plugin.HelloWorld.hello-world info test.qml
+\endcode
+
+If \c{test.qml} looks like
+\quotefromfile{tutorials/helloworld/chapter2/test.qml}
+\skipto import
+\printuntil
+
+we will get
+\badcode
+Info: test.qml:22:26: Incorrect greeting [Plugin.HelloWorld.hello-world]
+ MyText { text: "Goodbye world!" }
+ ^^^^^^^^^^^^^^^^
+Info: test.qml:19:19: Incorrect greeting [Plugin.HelloWorld.hello-world]
+ Text { text: "Goodbye world!" }
+\endcode
+
+as the output. We can make a few observations here:
+\list
+\li The first \c{Text} does contain the expected greeting, so there's no warning
+\li The second \c{Text} would at runtime have the wrong warning (\c{"Hello"}
+ instead of \c{"Hello world"}. However, this cannot be detected by qmllint
+ (in general), as there's no literal binding, but a binding to another property.
+ As we only check literal bindings, we simply skip over this binding.
+\li For the literal binding in the third \c{Text} element, we correctly warn about
+ the wrong greeting.
+\li As \c{NotText} does not derive from \c{Text}, the analysis will skip it, as
+ the \c{inherits} check will discard it.
+\li The custom \c{MyText} element inherits from \c{Text}, and consequently we
+ see the expected warning.
+\endlist
+
+In summary, we've seen the steps necessary to extend \c{qmllint} with custom passes,
+and have also become aware of the limitations of static checks.
+
+*/
+
+/*!
+\page qqmlsa-tutorial3.html
+\title QML Static Analysis 3 - Fixit Hints
+\previouspage QML Static Analysis 2 - Custom Pass
+
+In this chapter we learn how to improve our custom warnings by amending them
+with fixit hints.
+
+So far, we only created warning messages. However, sometimes we also want to
+add a tip for the user how to fix the code. For that, we can pass an instance
+of \c FixSuggestion to \l{QQmlSA::GenericPass::emitWarning}{emitWarning}.
+A fix suggestion always consists of a description of what should be fixed, and
+the location where it should apply. It can also feature a replacement text.
+By default, the replacement text is only shown in the diagnostic message.
+By calling \c{setAutoApplicable(true)} on the \c{FixSuggestion}, the user
+can however apply the fix automatically via qmllint or the QML language
+server.
+It is important to only mark the suggestion as auto-applicable if the
+resulting code is valid.
+*/
diff --git a/src/qmlcompiler/qcoloroutput.cpp b/src/qmlcompiler/qcoloroutput.cpp
index b79fc86caf..8e0d57faf5 100644
--- a/src/qmlcompiler/qcoloroutput.cpp
+++ b/src/qmlcompiler/qcoloroutput.cpp
@@ -17,17 +17,17 @@ class QColorOutputPrivate
public:
QColorOutputPrivate()
{
- /* - QIODevice::Unbuffered because we want it to appear when the user actually calls,
- * performance is considered of lower priority.
- */
- m_out.open(stderr, QIODevice::WriteOnly | QIODevice::Unbuffered);
m_coloringEnabled = isColoringPossible();
}
static const char *const foregrounds[];
static const char *const backgrounds[];
- inline void write(const QString &msg) { m_out.write(msg.toLocal8Bit()); }
+ inline void write(const QString &msg)
+ {
+ const QByteArray encodedMsg = msg.toLocal8Bit();
+ fwrite(encodedMsg.constData(), size_t(1), size_t(encodedMsg.size()), stderr);
+ }
static QString escapeCode(const QString &in)
{
@@ -58,7 +58,7 @@ private:
bool m_coloringEnabled = false;
bool m_silent = false;
- /*!
+ /*
Returns true if it's suitable to send colored output to \c stderr.
*/
inline bool isColoringPossible() const
@@ -72,7 +72,7 @@ private:
/* We use QFile::handle() to get the file descriptor. It's a bit unsure
* whether it's 2 on all platforms and in all cases, so hopefully this layer
* of abstraction helps handle such cases. */
- return isatty(m_out.handle());
+ return isatty(fileno(stderr));
#endif
}
};
@@ -109,18 +109,18 @@ const char *const QColorOutputPrivate::backgrounds[] =
};
/*!
- \class ColorOutput
+ \class QColorOutput
\nonreentrant
\brief Outputs colored messages to \c stderr.
\internal
- ColorOutput is a convenience class for outputting messages to \c
- stderr using color escape codes, as mandated in ECMA-48. ColorOutput
+ QColorOutput is a convenience class for outputting messages to \c
+ stderr using color escape codes, as mandated in ECMA-48. QColorOutput
will only color output when it is detected to be suitable. For
instance, if \c stderr is detected to be attached to a file instead
of a TTY, no coloring will be done.
- ColorOutput does its best attempt. but it is generally undefined
+ QColorOutput does its best attempt. but it is generally undefined
what coloring or effect the various coloring flags has. It depends
strongly on what terminal software that is being used.
@@ -129,14 +129,14 @@ const char *const QColorOutputPrivate::backgrounds[] =
escaped the backslash. That's why we below use characters with
value 0x1B.
- It can be convenient to subclass ColorOutput with a private scope,
+ It can be convenient to subclass QColorOutput with a private scope,
such that the functions are directly available in the class using
it.
\section1 Usage
To output messages, call write() or writeUncolored(). write() takes
- as second argument an integer, which ColorOutput uses as a lookup
+ as second argument an integer, which QColorOutput uses as a lookup
key to find the color it should color the text in. The mapping from
keys to colors is done using insertMapping(). Typically this is used
by having enums for the various kinds of messages, which
@@ -149,9 +149,9 @@ const char *const QColorOutputPrivate::backgrounds[] =
Important
};
- ColorOutput output;
- output.insertMapping(Error, ColorOutput::RedForeground);
- output.insertMapping(Import, ColorOutput::BlueForeground);
+ QColorOutput output;
+ output.insertMapping(Error, QColorOutput::RedForeground);
+ output.insertMapping(Import, QColorOutput::BlueForeground);
output.write("This is important", Important);
output.write("Jack, I'm only the selected official!", Error);
@@ -165,7 +165,8 @@ const char *const QColorOutputPrivate::backgrounds[] =
*/
/*!
- \enum ColorOutput::ColorCodeComponent
+ \internal
+ \enum QColorOutput::ColorCodeComponent
\value BlackForeground
\value BlueForeground
\value GreenForeground
@@ -190,13 +191,14 @@ const char *const QColorOutputPrivate::backgrounds[] =
\value PurpleBackground
\value BrownBackground
- \value DefaultColor ColorOutput performs no coloring. This typically
+ \value DefaultColor QColorOutput performs no coloring. This typically
means black on white or white on black, depending
on the settings of the user's terminal.
*/
/*!
- Constructs a ColorOutput instance, ready for use.
+ \internal
+ Constructs a QColorOutput instance, ready for use.
*/
QColorOutput::QColorOutput() : d(new QColorOutputPrivate) {}
@@ -207,11 +209,12 @@ bool QColorOutput::isSilent() const { return d->isSilent(); }
void QColorOutput::setSilent(bool silent) { d->setSilent(silent); }
/*!
+ \internal
Sends \a message to \c stderr, using the color looked up in the color mapping using \a colorID.
If \a color isn't available in the color mapping, result and behavior is undefined.
- If \a colorID is 0, which is the default value, the previously used coloring is used. ColorOutput
+ If \a colorID is 0, which is the default value, the previously used coloring is used. QColorOutput
is initialized to not color at all.
If \a message is empty, effects are undefined.
@@ -241,10 +244,11 @@ void QColorOutput::writePrefixedMessage(const QString &message, QtMsgType type,
}
/*!
+ \internal
Writes \a message to \c stderr as if for instance
QTextStream would have been used, and adds a line ending at the end.
- This function can be practical to use such that one can use ColorOutput for all forms of writing.
+ This function can be practical to use such that one can use QColorOutput for all forms of writing.
*/
void QColorOutput::writeUncolored(const QString &message)
{
@@ -253,6 +257,7 @@ void QColorOutput::writeUncolored(const QString &message)
}
/*!
+ \internal
Treats \a message and \a colorID identically to write(), but instead of writing
\a message to \c stderr, it is prepared for being written to \c stderr, but is then
returned.
@@ -309,7 +314,8 @@ QString QColorOutput::colorify(const QStringView message, int colorID) const
}
/*!
- Adds a color mapping from \a colorID to \a colorCode, for this ColorOutput instance.
+ \internal
+ Adds a color mapping from \a colorID to \a colorCode, for this QColorOutput instance.
*/
void QColorOutput::insertMapping(int colorID, const ColorCode colorCode)
{
diff --git a/src/qmlcompiler/qcoloroutput_p.h b/src/qmlcompiler/qcoloroutput_p.h
index c53b666802..af80e13f7e 100644
--- a/src/qmlcompiler/qcoloroutput_p.h
+++ b/src/qmlcompiler/qcoloroutput_p.h
@@ -14,7 +14,7 @@
//
// We mean it.
-#include <private/qtqmlcompilerexports_p.h>
+#include <qtqmlcompilerexports.h>
#include <QtCore/private/qglobal_p.h>
#include <QtCore/qscopedpointer.h>
@@ -24,7 +24,7 @@ QT_BEGIN_NAMESPACE
class QColorOutputPrivate;
-class Q_QMLCOMPILER_PRIVATE_EXPORT QColorOutput
+class Q_QMLCOMPILER_EXPORT QColorOutput
{
enum
{
diff --git a/src/qmlcompiler/qdeferredpointer_p.h b/src/qmlcompiler/qdeferredpointer_p.h
index 481c0e27ae..4bd3b18326 100644
--- a/src/qmlcompiler/qdeferredpointer_p.h
+++ b/src/qmlcompiler/qdeferredpointer_p.h
@@ -14,7 +14,7 @@
//
// We mean it.
-#include <private/qtqmlcompilerexports_p.h>
+#include <qtqmlcompilerexports.h>
#include <QtCore/private/qglobal_p.h>
#include <QtCore/qsharedpointer.h>
@@ -47,18 +47,18 @@ class QDeferredSharedPointer
public:
using Factory = QDeferredFactory<std::remove_const_t<T>>;
- QDeferredSharedPointer() = default;
+ Q_NODISCARD_CTOR QDeferredSharedPointer() = default;
- QDeferredSharedPointer(QSharedPointer<T> data)
- : m_data(data)
+ Q_NODISCARD_CTOR QDeferredSharedPointer(QSharedPointer<T> data)
+ : m_data(std::move(data))
{}
- QDeferredSharedPointer(QWeakPointer<T> data)
- : m_data(data)
+ Q_NODISCARD_CTOR QDeferredSharedPointer(QWeakPointer<T> data)
+ : m_data(std::move(data))
{}
- QDeferredSharedPointer(QSharedPointer<T> data, QSharedPointer<Factory> factory)
- : m_data(data), m_factory(factory)
+ Q_NODISCARD_CTOR QDeferredSharedPointer(QSharedPointer<T> data, QSharedPointer<Factory> factory)
+ : m_data(std::move(data)), m_factory(std::move(factory))
{
// You have to provide a valid pointer if you provide a factory. We cannot allocate the
// pointer for you because then two copies of the same QDeferredSharedPointer will diverge
@@ -66,7 +66,7 @@ public:
Q_ASSERT(!m_data.isNull() || m_factory.isNull());
}
- operator QSharedPointer<T>() const
+ [[nodiscard]] operator QSharedPointer<T>() const
{
lazyLoad();
return m_data;
@@ -74,8 +74,8 @@ public:
operator QDeferredSharedPointer<const T>() const { return { m_data, m_factory }; }
- T &operator*() const { return QSharedPointer<T>(*this).operator*(); }
- T *operator->() const { return QSharedPointer<T>(*this).operator->(); }
+ [[nodiscard]] T &operator*() const { return QSharedPointer<T>(*this).operator*(); }
+ [[nodiscard]] T *operator->() const { return QSharedPointer<T>(*this).operator->(); }
bool isNull() const
{
@@ -85,8 +85,8 @@ public:
explicit operator bool() const noexcept { return !isNull(); }
bool operator !() const noexcept { return isNull(); }
- T *data() const { return QSharedPointer<T>(*this).data(); }
- T *get() const { return data(); }
+ [[nodiscard]] T *data() const { return QSharedPointer<T>(*this).data(); }
+ [[nodiscard]] T *get() const { return data(); }
friend size_t qHash(const QDeferredSharedPointer &ptr, size_t seed = 0)
{
@@ -155,6 +155,14 @@ public:
return (m_factory && m_factory->isValid()) ? m_factory.data() : nullptr;
}
+ void resetFactory(const Factory& newFactory) const
+ {
+ const bool wasAlreadyLoaded = !factory();
+ *m_factory = newFactory;
+ if (wasAlreadyLoaded)
+ lazyLoad();
+ }
+
private:
friend class QDeferredWeakPointer<T>;
@@ -177,31 +185,31 @@ class QDeferredWeakPointer
public:
using Factory = QDeferredFactory<std::remove_const_t<T>>;
- QDeferredWeakPointer() = default;
+ Q_NODISCARD_CTOR QDeferredWeakPointer() = default;
- QDeferredWeakPointer(const QDeferredSharedPointer<T> &strong)
+ Q_NODISCARD_CTOR QDeferredWeakPointer(const QDeferredSharedPointer<T> &strong)
: m_data(strong.m_data), m_factory(strong.m_factory)
{
}
- QDeferredWeakPointer(QWeakPointer<T> data, QWeakPointer<Factory> factory)
+ Q_NODISCARD_CTOR QDeferredWeakPointer(QWeakPointer<T> data, QWeakPointer<Factory> factory)
: m_data(data), m_factory(factory)
{}
- operator QWeakPointer<T>() const
+ [[nodiscard]] operator QWeakPointer<T>() const
{
lazyLoad();
return m_data;
}
- operator QDeferredSharedPointer<T>() const
+ [[nodiscard]] operator QDeferredSharedPointer<T>() const
{
return QDeferredSharedPointer<T>(m_data.toStrongRef(), m_factory.toStrongRef());
}
operator QDeferredWeakPointer<const T>() const { return {m_data, m_factory}; }
- QSharedPointer<T> toStrongRef() const
+ [[nodiscard]] QSharedPointer<T> toStrongRef() const
{
return QWeakPointer<T>(*this).toStrongRef();
}
diff --git a/src/qmlcompiler/qqmljsbasicblocks.cpp b/src/qmlcompiler/qqmljsbasicblocks.cpp
index 3583aa2405..1696016f79 100644
--- a/src/qmlcompiler/qqmljsbasicblocks.cpp
+++ b/src/qmlcompiler/qqmljsbasicblocks.cpp
@@ -2,48 +2,109 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "qqmljsbasicblocks_p.h"
+#include "qqmljsutils_p.h"
+
+#include <QtQml/private/qv4instr_moth_p.h>
QT_BEGIN_NAMESPACE
-template<typename Container>
-void deduplicate(Container &container)
+using namespace Qt::Literals::StringLiterals;
+
+DEFINE_BOOL_CONFIG_OPTION(qv4DumpBasicBlocks, QV4_DUMP_BASIC_BLOCKS)
+DEFINE_BOOL_CONFIG_OPTION(qv4ValidateBasicBlocks, QV4_VALIDATE_BASIC_BLOCKS)
+
+void QQmlJSBasicBlocks::dumpBasicBlocks()
{
- std::sort(container.begin(), container.end());
- auto erase = std::unique(container.begin(), container.end());
- container.erase(erase, container.end());
+ qDebug().noquote() << "=== Basic Blocks for \"%1\""_L1.arg(m_context->name);
+ for (const auto &[blockOffset, block] : m_basicBlocks) {
+ QDebug debug = qDebug().nospace();
+ debug << "Block " << (blockOffset < 0 ? "Function prolog"_L1 : QString::number(blockOffset))
+ << ":\n";
+ debug << " jumpOrigins[" << block.jumpOrigins.size() << "]: ";
+ for (auto origin : block.jumpOrigins) {
+ debug << origin << ", ";
+ }
+ debug << "\n readRegisters[" << block.readRegisters.size() << "]: ";
+ for (auto reg : block.readRegisters) {
+ debug << reg << ", ";
+ }
+ debug << "\n jumpTarget: " << block.jumpTarget;
+ debug << "\n jumpIsUnConditional: " << block.jumpIsUnconditional;
+ debug << "\n isReturnBlock: " << block.isReturnBlock;
+ debug << "\n isThrowBlock: " << block.isThrowBlock;
+ }
+ qDebug() << "\n";
}
-static bool instructionManipulatesContext(QV4::Moth::Instr::Type type)
+void QQmlJSBasicBlocks::dumpDOTGraph()
{
- 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;
+ QString output;
+ QTextStream s{ &output };
+ s << "=== Basic Blocks Graph in DOT format for \"%1\" (spaces are encoded as"
+ " &#160; to preserve formatting)\n"_L1.arg(m_context->name);
+ s << "digraph BasicBlocks {\n"_L1;
+
+ QFlatMap<int, BasicBlock> blocks{ m_basicBlocks };
+ for (const auto &[blockOffset, block] : blocks) {
+ for (int originOffset : block.jumpOrigins) {
+ const auto originBlockIt = basicBlockForInstruction(blocks, originOffset);
+ const auto isBackEdge = originOffset > blockOffset && originBlockIt->second.jumpIsUnconditional;
+ s << " %1 -> %2%3\n"_L1.arg(QString::number(originBlockIt.key()))
+ .arg(QString::number(blockOffset))
+ .arg(isBackEdge ? " [color=blue]"_L1 : ""_L1);
+ }
+ }
+
+ for (const auto &[blockOffset, block] : blocks) {
+ if (blockOffset < 0) {
+ s << " %1 [shape=record, fontname=\"Monospace\", label=\"Function Prolog\"]\n"_L1
+ .arg(QString::number(blockOffset));
+ continue;
+ }
+
+ auto nextBlockIt = blocks.lower_bound(blockOffset + 1);
+ int nextBlockOffset = nextBlockIt == blocks.end() ? m_context->code.size() : nextBlockIt->first;
+ QString dump = QV4::Moth::dumpBytecode(
+ m_context->code.constData(), m_context->code.size(), m_context->locals.size(),
+ m_context->formals->length(), blockOffset, nextBlockOffset - 1,
+ m_context->lineAndStatementNumberMapping);
+ dump = dump.replace(" "_L1, "&#160;"_L1); // prevent collapse of extra whitespace for formatting
+ dump = dump.replace("\n"_L1, "\\l"_L1); // new line + left aligned
+ s << " %1 [shape=record, fontname=\"Monospace\", label=\"{Block %1: | %2}\"]\n"_L1
+ .arg(QString::number(blockOffset))
+ .arg(dump);
+ }
+ s << "}\n"_L1;
+
+ // Have unique names to prevent overwriting of functions with the same name (eg. anonymous functions).
+ static int functionCount = 0;
+ static const auto dumpFolderPath = qEnvironmentVariable("QV4_DUMP_BASIC_BLOCKS");
+
+ QString expressionName = m_context->name == ""_L1
+ ? "anonymous"_L1
+ : QString(m_context->name).replace(" "_L1, "_"_L1);
+ QString fileName = "function"_L1 + QString::number(functionCount++) + "_"_L1 + expressionName + ".gv"_L1;
+ QFile dumpFile(dumpFolderPath + (dumpFolderPath.endsWith("/"_L1) ? ""_L1 : "/"_L1) + fileName);
+
+ if (dumpFolderPath == "-"_L1 || dumpFolderPath == "1"_L1 || dumpFolderPath == "true"_L1) {
+ qDebug().noquote() << output;
+ } else {
+ if (!dumpFile.open(QIODeviceBase::Truncate | QIODevice::WriteOnly)) {
+ qDebug() << "Error: Could not open file to dump the basic blocks into";
+ } else {
+ dumpFile.write(("//"_L1 + output).toLatin1().toStdString().c_str());
+ dumpFile.close();
+ }
}
- return false;
}
-QQmlJSCompilePass::InstructionAnnotations QQmlJSBasicBlocks::run(
- const Function *function,
- const InstructionAnnotations &annotations)
+QQmlJSCompilePass::BlocksAndAnnotations
+QQmlJSBasicBlocks::run(const Function *function, QQmlJSAotCompiler::Flags compileFlags,
+ bool &basicBlocksValidationFailed)
{
+ basicBlocksValidationFailed = false;
+
m_function = function;
- m_annotations = annotations;
for (int i = 0, end = function->argumentTypes.size(); i != end; ++i) {
InstructionAnnotation annotation;
@@ -59,7 +120,11 @@ QQmlJSCompilePass::InstructionAnnotations QQmlJSBasicBlocks::run(
m_annotations[-annotation.changedRegisterIndex] = annotation;
}
+ // Insert the function prolog block followed by the first "real" block.
m_basicBlocks.insert_or_assign(m_annotations.begin().key(), BasicBlock());
+ BasicBlock zeroBlock;
+ zeroBlock.jumpOrigins.append(m_basicBlocks.begin().key());
+ m_basicBlocks.insert(0, zeroBlock);
const QByteArray byteCode = function->code;
decode(byteCode.constData(), static_cast<uint>(byteCode.size()));
@@ -74,16 +139,27 @@ QQmlJSCompilePass::InstructionAnnotations QQmlJSBasicBlocks::run(
it->second.jumpIsUnconditional = false;
}
+ m_skipUntilNextLabel = false;
+
reset();
decode(byteCode.constData(), static_cast<uint>(byteCode.size()));
for (auto it = m_basicBlocks.begin(), end = m_basicBlocks.end(); it != end; ++it)
- deduplicate(it->second.jumpOrigins);
+ QQmlJSUtils::deduplicate(it->second.jumpOrigins);
+ }
+
+ if (compileFlags.testFlag(QQmlJSAotCompiler::ValidateBasicBlocks) || qv4ValidateBasicBlocks()) {
+ if (auto validationResult = basicBlocksValidation(); !validationResult.success) {
+ qDebug() << "Basic blocks validation failed: %1."_L1.arg(validationResult.errorMessage);
+ basicBlocksValidationFailed = true;
+ }
+ }
+
+ if (qv4DumpBasicBlocks()) {
+ dumpBasicBlocks();
+ dumpDOTGraph();
}
- populateBasicBlocks();
- populateReaderLocations();
- adjustTypes();
- return std::move(m_annotations);
+ return { std::move(m_basicBlocks), std::move(m_annotations) };
}
QV4::Moth::ByteCodeHandler::Verdict QQmlJSBasicBlocks::startInstruction(QV4::Moth::Instr::Type type)
@@ -132,22 +208,64 @@ void QQmlJSBasicBlocks::generate_JumpNotUndefined(int offset)
processJump(offset, Conditional);
}
+void QQmlJSBasicBlocks::generate_IteratorNext(int value, int offset)
+{
+ Q_UNUSED(value);
+ processJump(offset, Conditional);
+}
+
+void QQmlJSBasicBlocks::generate_GetOptionalLookup(int index, int offset)
+{
+ Q_UNUSED(index);
+ processJump(offset, Conditional);
+}
+
void QQmlJSBasicBlocks::generate_Ret()
{
+ auto currentBlock = basicBlockForInstruction(m_basicBlocks, currentInstructionOffset());
+ currentBlock.value().isReturnBlock = true;
m_skipUntilNextLabel = true;
}
void QQmlJSBasicBlocks::generate_ThrowException()
{
+ auto currentBlock = basicBlockForInstruction(m_basicBlocks, currentInstructionOffset());
+ currentBlock.value().isThrowBlock = true;
m_skipUntilNextLabel = true;
}
-void QQmlJSBasicBlocks::generate_DefineArray(int argc, int)
+void QQmlJSBasicBlocks::generate_DefineArray(int argc, int argv)
+{
+ if (argc == 0)
+ return; // empty array/list, nothing to do
+
+ m_objectAndArrayDefinitions.append({
+ currentInstructionOffset(), ObjectOrArrayDefinition::ArrayClassId, argc, argv
+ });
+}
+
+void QQmlJSBasicBlocks::generate_DefineObjectLiteral(int internalClassId, int argc, int argv)
{
if (argc == 0)
+ return;
+
+ m_objectAndArrayDefinitions.append({ currentInstructionOffset(), internalClassId, argc, argv });
+}
+
+void QQmlJSBasicBlocks::generate_Construct(int func, int argc, int argv)
+{
+ Q_UNUSED(func)
+ if (argc == 0)
return; // empty array/list, nothing to do
- m_arrayDefinitions.append(currentInstructionOffset());
+ m_objectAndArrayDefinitions.append({
+ currentInstructionOffset(),
+ argc == 1
+ ? ObjectOrArrayDefinition::ArrayConstruct1ArgId
+ : ObjectOrArrayDefinition::ArrayClassId,
+ argc,
+ argv
+ });
}
void QQmlJSBasicBlocks::processJump(int offset, JumpMode mode)
@@ -156,400 +274,76 @@ void QQmlJSBasicBlocks::processJump(int offset, JumpMode mode)
m_hadBackJumps = true;
const int jumpTarget = absoluteOffset(offset);
Q_ASSERT(!m_basicBlocks.isEmpty());
- auto currentBlock = m_basicBlocks.lower_bound(currentInstructionOffset());
- if (currentBlock == m_basicBlocks.end() || currentBlock->first != currentInstructionOffset())
- --currentBlock;
+ auto currentBlock = basicBlockForInstruction(m_basicBlocks, currentInstructionOffset());
currentBlock->second.jumpTarget = jumpTarget;
currentBlock->second.jumpIsUnconditional = (mode == Unconditional);
m_basicBlocks[jumpTarget].jumpOrigins.append(currentInstructionOffset());
if (mode == Unconditional)
m_skipUntilNextLabel = true;
else
- m_basicBlocks[nextInstructionOffset()].jumpOrigins.append(currentInstructionOffset());
+ m_basicBlocks.insert(nextInstructionOffset(), BasicBlock());
}
-template<typename ContainerA, typename ContainerB>
-static bool containsAny(const ContainerA &container, const ContainerB &elements)
+QQmlJSCompilePass::BasicBlocks::iterator QQmlJSBasicBlocks::basicBlockForInstruction(
+ QFlatMap<int, BasicBlock> &container, int instructionOffset)
{
- for (const auto &element : elements) {
- if (container.contains(element))
- return true;
- }
- return false;
+ auto block = container.lower_bound(instructionOffset);
+ if (block == container.end() || block->first != instructionOffset)
+ --block;
+ return block;
}
-template<typename ContainerA, typename ContainerB>
-static bool containsAll(const ContainerA &container, const ContainerB &elements)
+QQmlJSCompilePass::BasicBlocks::const_iterator QQmlJSBasicBlocks::basicBlockForInstruction(
+ const BasicBlocks &container, int instructionOffset)
{
- for (const auto &element : elements) {
- if (!container.contains(element))
- return false;
- }
- return true;
+ return basicBlockForInstruction(const_cast<BasicBlocks &>(container), instructionOffset);
}
-template<class Key, class T, class Compare = std::less<Key>,
- class KeyContainer = QList<Key>, class MappedContainer = QList<T>>
-class NewFlatMap
+QQmlJSBasicBlocks::BasicBlocksValidationResult QQmlJSBasicBlocks::basicBlocksValidation()
{
-public:
- using OriginalFlatMap = QFlatMap<Key, T, Compare, KeyContainer, MappedContainer>;
-
- void appendOrdered(const typename OriginalFlatMap::iterator &i) {
- keys.append(i.key());
- values.append(i.value());
- }
-
- OriginalFlatMap take() {
- OriginalFlatMap result(Qt::OrderedUniqueRange, std::move(keys), std::move(values));
- keys.clear();
- values.clear();
- return result;
- }
-
-private:
- typename OriginalFlatMap::key_container_type keys;
- typename OriginalFlatMap::mapped_container_type values;
-};
-
-struct PendingBlock
-{
- QList<int> conversions;
- int start = -1;
- bool registerActive = false;
-};
-
-void QQmlJSBasicBlocks::populateReaderLocations()
-{
- using NewInstructionAnnotations = NewFlatMap<int, InstructionAnnotation>;
-
- bool erasedReaders = false;
- auto eraseDeadStore = [&](const InstructionAnnotations::iterator &it) {
- auto reader = m_readerLocations.find(it.key());
- if (reader != m_readerLocations.end()
- && (reader->typeReaders.isEmpty()
- || reader->registerReadersAndConversions.isEmpty())) {
-
- if (it->second.isRename) {
- // If it's a rename, it doesn't "own" its output type. The type may
- // still be read elsewhere, even if this register isn't. However, we're
- // not interested in the variant or any other details of the register.
- // Therefore just delete it.
- it->second.changedRegisterIndex = InvalidRegister;
- it->second.changedRegister = QQmlJSRegisterContent();
- } else {
- // void the output, rather than deleting it. We still need its variant.
- m_typeResolver->adjustTrackedType(
- it->second.changedRegister.storedType(),
- m_typeResolver->voidType());
- m_typeResolver->adjustTrackedType(
- m_typeResolver->containedType(it->second.changedRegister),
- m_typeResolver->voidType());
- }
- m_readerLocations.erase(reader);
-
- // If it's not a label and has no side effects, we can drop the instruction.
- if (!it->second.hasSideEffects) {
- if (!it->second.readRegisters.isEmpty()) {
- it->second.readRegisters.clear();
- erasedReaders = true;
- }
- if (m_basicBlocks.find(it.key()) == m_basicBlocks.end())
- return true;
- }
- }
- return false;
- };
-
- NewInstructionAnnotations newAnnotations;
- for (auto writeIt = m_annotations.begin(), writeEnd = m_annotations.end();
- writeIt != writeEnd; ++writeIt) {
- const int writtenRegister = writeIt->second.changedRegisterIndex;
- if (writtenRegister == InvalidRegister) {
- newAnnotations.appendOrdered(writeIt);
- continue;
- }
-
- RegisterAccess &access = m_readerLocations[writeIt.key()];
- access.trackedRegister = writtenRegister;
- if (writeIt->second.changedRegister.isConversion()) {
- // If it's a conversion, we have to check for all readers of the conversion origins.
- // This happens at jump targets where different types are merged. A StoreReg or similar
- // instruction must be optimized out if none of the types it can hold is read anymore.
- access.trackedTypes = writeIt->second.changedRegister.conversionOrigins();
- } else {
- access.trackedTypes.append(
- m_typeResolver->trackedContainedType(writeIt->second.changedRegister));
- }
-
- auto blockIt = m_basicBlocks.lower_bound(writeIt.key());
- if (blockIt == m_basicBlocks.end() || blockIt->first != writeIt.key())
- --blockIt;
-
- QList<PendingBlock> blocks = { { {}, blockIt->first, true } };
- QHash<int, PendingBlock> processedBlocks;
- bool isFirstBlock = true;
-
- while (!blocks.isEmpty()) {
- const PendingBlock block = blocks.takeLast();
-
- // We can re-enter the first block from the beginning.
- // We will then find any reads before the write we're currently examining.
- if (!isFirstBlock)
- processedBlocks.insert(block.start, block);
-
- auto nextBlock = m_basicBlocks.find(block.start);
- auto currentBlock = nextBlock++;
- bool registerActive = block.registerActive;
- QList<int> conversions = block.conversions;
-
- const auto blockEnd = (nextBlock == m_basicBlocks.end())
- ? m_annotations.end()
- : m_annotations.find(nextBlock->first);
-
- auto blockInstr = isFirstBlock
- ? (writeIt + 1)
- : m_annotations.find(currentBlock->first);
- for (; blockInstr != blockEnd; ++blockInstr) {
- if (registerActive
- && blockInstr->second.typeConversions.contains(writtenRegister)) {
- conversions.append(blockInstr.key());
- }
-
- for (auto readIt = blockInstr->second.readRegisters.constBegin(),
- end = blockInstr->second.readRegisters.constEnd();
- readIt != end; ++readIt) {
- if (!blockInstr->second.isRename && containsAny(
- readIt->second.content.conversionOrigins(), access.trackedTypes)) {
- Q_ASSERT(readIt->second.content.isConversion());
- access.typeReaders[blockInstr.key()]
- = readIt->second.content.conversionResult();
- }
- if (registerActive && readIt->first == writtenRegister)
- access.registerReadersAndConversions[blockInstr.key()] = conversions;
- }
-
- if (blockInstr->second.changedRegisterIndex == writtenRegister) {
- conversions.clear();
- registerActive = false;
- }
- }
-
- auto scheduleBlock = [&](int blockStart) {
- // If we find that an already processed block has the register activated by this jump,
- // we need to re-evaluate it. We also need to propagate any newly found conversions.
- const auto processed = processedBlocks.find(blockStart);
- if (processed == processedBlocks.end())
- blocks.append({conversions, blockStart, registerActive});
- else if (registerActive && !processed->registerActive)
- blocks.append({conversions, blockStart, registerActive});
- else if (!containsAll(processed->conversions, conversions))
- blocks.append({processed->conversions + conversions, blockStart, registerActive});
- };
-
- if (!currentBlock->second.jumpIsUnconditional && nextBlock != m_basicBlocks.end())
- scheduleBlock(nextBlock->first);
-
- const int jumpTarget = currentBlock->second.jumpTarget;
- if (jumpTarget != -1)
- scheduleBlock(jumpTarget);
-
- if (isFirstBlock)
- isFirstBlock = false;
- }
-
- if (!eraseDeadStore(writeIt))
- newAnnotations.appendOrdered(writeIt);
+ if (m_basicBlocks.empty())
+ return {};
+
+ const QFlatMap<int, BasicBlock> blocks{ m_basicBlocks };
+ QList<QFlatMap<int, BasicBlock>::const_iterator> returnOrThrowBlocks;
+ for (auto it = blocks.cbegin(); it != blocks.cend(); ++it) {
+ if (it.value().isReturnBlock || it.value().isThrowBlock)
+ returnOrThrowBlocks.append(it);
}
- m_annotations = newAnnotations.take();
-
- while (erasedReaders) {
- erasedReaders = false;
-
- for (auto it = m_annotations.begin(), end = m_annotations.end(); it != end; ++it) {
- InstructionAnnotation &instruction = it->second;
- if (instruction.changedRegisterIndex < InvalidRegister) {
- newAnnotations.appendOrdered(it);
- continue;
- }
-
- auto readers = m_readerLocations.find(it.key());
- if (readers != m_readerLocations.end()) {
- for (auto typeIt = readers->typeReaders.begin();
- typeIt != readers->typeReaders.end();) {
- if (m_annotations.contains(typeIt.key()))
- ++typeIt;
- else
- typeIt = readers->typeReaders.erase(typeIt);
- }
-
- for (auto registerIt = readers->registerReadersAndConversions.begin();
- registerIt != readers->registerReadersAndConversions.end();) {
- if (m_annotations.contains(registerIt.key()))
- ++registerIt;
- else
- registerIt = readers->registerReadersAndConversions.erase(registerIt);
- }
- }
-
- if (!eraseDeadStore(it))
- newAnnotations.appendOrdered(it);
- }
- m_annotations = newAnnotations.take();
+ // 1. Return blocks and throw blocks must not have a jump target
+ for (const auto &it : returnOrThrowBlocks) {
+ if (it.value().jumpTarget != -1)
+ return { false, "Return or throw block jumps to somewhere"_L1 };
}
-}
-
-bool QQmlJSBasicBlocks::canMove(int instructionOffset, const RegisterAccess &access) const
-{
- if (access.registerReadersAndConversions.size() != 1)
- return false;
- const auto basicBlockForInstruction = [this](int instruction) {
- auto block = m_basicBlocks.lower_bound(instruction);
- if (block == m_basicBlocks.end() || block.key() == instruction)
- return block;
- Q_ASSERT(block.key() > instruction);
- if (block == m_basicBlocks.begin())
- return m_basicBlocks.end();
- return --block;
- };
- return basicBlockForInstruction(instructionOffset)
- == basicBlockForInstruction(access.registerReadersAndConversions.begin().key());
-}
-void QQmlJSBasicBlocks::adjustTypes()
-{
- using NewVirtualRegisters = NewFlatMap<int, VirtualRegister>;
-
- QHash<int, QList<int>> liveConversions;
- QHash<int, QList<int>> movableReads;
-
- const auto handleRegisterReadersAndConversions
- = [&](QHash<int, RegisterAccess>::const_iterator it) {
- for (auto conversions = it->registerReadersAndConversions.constBegin(),
- end = it->registerReadersAndConversions.constEnd(); conversions != end;
- ++conversions) {
- if (conversions->isEmpty() && canMove(it.key(), it.value()))
- movableReads[conversions.key()].append(it->trackedRegister);
- for (int conversion : *conversions)
- liveConversions[conversion].append(it->trackedRegister);
+ // 2. The basic blocks graph must be connected.
+ QSet<int> visitedBlockOffsets;
+ QList<QFlatMap<int, BasicBlock>::const_iterator> toVisit;
+ toVisit.append(returnOrThrowBlocks);
+
+ while (!toVisit.empty()) {
+ const auto &[offset, block] = *toVisit.takeLast();
+ visitedBlockOffsets.insert(offset);
+ for (int originOffset : block.jumpOrigins) {
+ const auto originBlock = basicBlockForInstruction(blocks, originOffset);
+ if (visitedBlockOffsets.find(originBlock.key()) == visitedBlockOffsets.end()
+ && !toVisit.contains(originBlock))
+ toVisit.append(originBlock);
}
- };
-
- // Handle the array definitions first.
- // Changing the array type changes the expected element types.
- for (int instructionOffset : m_arrayDefinitions) {
- auto it = m_readerLocations.find(instructionOffset);
- if (it == m_readerLocations.end())
- continue;
-
- const InstructionAnnotation &annotation = m_annotations[instructionOffset];
-
- Q_ASSERT(it->trackedTypes.size() == 1);
- Q_ASSERT(it->trackedTypes[0] == m_typeResolver->containedType(annotation.changedRegister));
- Q_ASSERT(!annotation.readRegisters.isEmpty());
-
- m_typeResolver->adjustTrackedType(it->trackedTypes[0], it->typeReaders.values());
-
- // Now we don't adjust the type we store, but rather the type we expect to read. We
- // can do this because we've tracked the read type when we defined the array in
- // QQmlJSTypePropagator.
- if (QQmlJSScope::ConstPtr valueType = it->trackedTypes[0]->valueType()) {
- m_typeResolver->adjustTrackedType(
- m_typeResolver->containedType(annotation.readRegisters.begin().value().content),
- valueType);
- }
-
- handleRegisterReadersAndConversions(it);
- m_readerLocations.erase(it);
}
- for (auto it = m_readerLocations.begin(), end = m_readerLocations.end(); it != end; ++it) {
- handleRegisterReadersAndConversions(it);
+ if (visitedBlockOffsets.size() != blocks.size())
+ return { false, "Basic blocks graph is not fully connected"_L1 };
- // There is always one first occurrence of any tracked type. Conversions don't change
- // the type.
- if (it->trackedTypes.size() != 1)
- continue;
-
- m_typeResolver->adjustTrackedType(it->trackedTypes[0], it->typeReaders.values());
+ // 3. A block's jump target must be the first offset of a block.
+ for (const auto &[blockOffset, block] : blocks) {
+ auto target = block.jumpTarget;
+ if (target != -1 && blocks.find(target) == blocks.end())
+ return { false, "Invalid jump; target is not the start of a block"_L1 };
}
- const auto transformRegister = [&](QQmlJSRegisterContent &content) {
- m_typeResolver->adjustTrackedType(
- content.storedType(),
- m_typeResolver->storedType(m_typeResolver->containedType(content)));
- };
-
- NewVirtualRegisters newRegisters;
- for (auto i = m_annotations.begin(), iEnd = m_annotations.end(); i != iEnd; ++i) {
- if (i->second.changedRegisterIndex != InvalidRegister)
- transformRegister(i->second.changedRegister);
-
- for (auto conversion = i->second.typeConversions.begin(),
- conversionEnd = i->second.typeConversions.end(); conversion != conversionEnd;
- ++conversion) {
- if (!liveConversions[i.key()].contains(conversion.key()))
- continue;
-
- QQmlJSScope::ConstPtr conversionResult = conversion->second.content.conversionResult();
- const auto conversionOrigins = conversion->second.content.conversionOrigins();
- QQmlJSScope::ConstPtr newResult;
- for (const auto &origin : conversionOrigins)
- newResult = m_typeResolver->merge(newResult, origin);
- m_typeResolver->adjustTrackedType(conversionResult, newResult);
- transformRegister(conversion->second.content);
- newRegisters.appendOrdered(conversion);
- }
- i->second.typeConversions = newRegisters.take();
-
- for (int movable : std::as_const(movableReads[i.key()]))
- i->second.readRegisters[movable].canMove = true;
- }
-}
-
-void QQmlJSBasicBlocks::populateBasicBlocks()
-{
- for (auto blockNext = m_basicBlocks.begin(), blockEnd = m_basicBlocks.end();
- blockNext != blockEnd;) {
-
- const auto blockIt = blockNext++;
- BasicBlock &block = blockIt->second;
- QList<QQmlJSScope::ConstPtr> writtenTypes;
- QList<int> writtenRegisters;
-
- const auto instrEnd = (blockNext == blockEnd)
- ? m_annotations.end()
- : m_annotations.find(blockNext->first);
- for (auto instrIt = m_annotations.find(blockIt->first); instrIt != instrEnd; ++instrIt) {
- const InstructionAnnotation &instruction = instrIt->second;
- for (auto it = instruction.readRegisters.begin(), end = instruction.readRegisters.end();
- it != end; ++it) {
- if (!instruction.isRename) {
- Q_ASSERT(it->second.content.isConversion());
- for (const QQmlJSScope::ConstPtr &origin :
- it->second.content.conversionOrigins()) {
- if (!writtenTypes.contains(origin))
- block.readTypes.append(origin);
- }
- }
- if (!writtenRegisters.contains(it->first))
- block.readRegisters.append(it->first);
- }
-
- // If it's just a renaming, the type has existed in a different register before.
- if (instruction.changedRegisterIndex != InvalidRegister) {
- if (!instruction.isRename) {
- writtenTypes.append(m_typeResolver->trackedContainedType(
- instruction.changedRegister));
- }
- writtenRegisters.append(instruction.changedRegisterIndex);
- }
- }
-
- deduplicate(block.readTypes);
- deduplicate(block.readRegisters);
- }
+ return {};
}
QT_END_NAMESPACE
diff --git a/src/qmlcompiler/qqmljsbasicblocks_p.h b/src/qmlcompiler/qqmljsbasicblocks_p.h
index ae95014dd1..a443d422ea 100644
--- a/src/qmlcompiler/qqmljsbasicblocks_p.h
+++ b/src/qmlcompiler/qqmljsbasicblocks_p.h
@@ -15,41 +15,39 @@
// We mean it.
-#include <private/qqmljscompilepass_p.h>
#include <private/qflatmap_p.h>
+#include <private/qqmljscompilepass_p.h>
+#include <private/qqmljscompiler_p.h>
QT_BEGIN_NAMESPACE
-class Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSBasicBlocks : public QQmlJSCompilePass
+class Q_QMLCOMPILER_EXPORT QQmlJSBasicBlocks : public QQmlJSCompilePass
{
public:
- struct BasicBlock {
- QList<int> jumpOrigins;
- QList<int> readRegisters;
- QList<QQmlJSScope::ConstPtr> readTypes;
- int jumpTarget = -1;
- bool jumpIsUnconditional = false;
- };
-
- QQmlJSBasicBlocks(const QV4::Compiler::JSUnitGenerator *unitGenerator,
+ QQmlJSBasicBlocks(const QV4::Compiler::Context *context,
+ const QV4::Compiler::JSUnitGenerator *unitGenerator,
const QQmlJSTypeResolver *typeResolver, QQmlJSLogger *logger)
- : QQmlJSCompilePass(unitGenerator, typeResolver, logger)
+ : QQmlJSCompilePass(unitGenerator, typeResolver, logger), m_context{ context }
{
}
~QQmlJSBasicBlocks() = default;
- InstructionAnnotations run(const Function *function, const InstructionAnnotations &annotations);
+ QQmlJSCompilePass::BlocksAndAnnotations run(const Function *function,
+ QQmlJSAotCompiler::Flags compileFlags,
+ bool &basicBlocksValidationFailed);
-private:
- struct RegisterAccess
- {
- QList<QQmlJSScope::ConstPtr> trackedTypes;
- QHash<int, QQmlJSScope::ConstPtr> typeReaders;
- QHash<int, QList<int>> registerReadersAndConversions;
- int trackedRegister;
- };
+ struct BasicBlocksValidationResult { bool success = true; QString errorMessage; };
+ BasicBlocksValidationResult basicBlocksValidation();
+
+ static BasicBlocks::iterator
+ basicBlockForInstruction(QFlatMap<int, BasicBlock> &container, int instructionOffset);
+ static BasicBlocks::const_iterator
+ basicBlockForInstruction(const QFlatMap<int, BasicBlock> &container, int instructionOffset);
+ QList<ObjectOrArrayDefinition> objectAndArrayDefinitions() const;
+
+private:
QV4::Moth::ByteCodeHandler::Verdict startInstruction(QV4::Moth::Instr::Type type) override;
void endInstruction(QV4::Moth::Instr::Type type) override;
@@ -58,23 +56,24 @@ private:
void generate_JumpFalse(int offset) override;
void generate_JumpNoException(int offset) override;
void generate_JumpNotUndefined(int offset) override;
+ void generate_IteratorNext(int value, int offset) override;
+ void generate_GetOptionalLookup(int index, int offset) override;
void generate_Ret() override;
void generate_ThrowException() override;
void generate_DefineArray(int argc, int argv) override;
+ void generate_DefineObjectLiteral(int internalClassId, int argc, int args) override;
+ void generate_Construct(int func, int argc, int argv) override;
enum JumpMode { Unconditional, Conditional };
void processJump(int offset, JumpMode mode);
- void populateBasicBlocks();
- void populateReaderLocations();
- void adjustTypes();
- bool canMove(int instructionOffset, const RegisterAccess &access) const;
-
- InstructionAnnotations m_annotations;
- QFlatMap<int, BasicBlock> m_basicBlocks;
- QHash<int, RegisterAccess> m_readerLocations;
- QList<int> m_arrayDefinitions;
+
+ void dumpBasicBlocks();
+ void dumpDOTGraph();
+
+ const QV4::Compiler::Context *m_context;
+ QList<ObjectOrArrayDefinition> m_objectAndArrayDefinitions;
bool m_skipUntilNextLabel = false;
bool m_hadBackJumps = false;
};
diff --git a/src/qmlcompiler/qqmljscodegenerator.cpp b/src/qmlcompiler/qqmljscodegenerator.cpp
index a1be6ae37a..5c98d7b2be 100644
--- a/src/qmlcompiler/qqmljscodegenerator.cpp
+++ b/src/qmlcompiler/qqmljscodegenerator.cpp
@@ -44,7 +44,6 @@ static bool isTypeStorable(const QQmlJSTypeResolver *resolver, const QQmlJSScope
{
return !type.isNull()
&& !resolver->equals(type, resolver->nullType())
- && !resolver->equals(type, resolver->emptyListType())
&& !resolver->equals(type, resolver->voidType());
}
@@ -54,11 +53,11 @@ QString QQmlJSCodeGenerator::castTargetName(const QQmlJSScope::ConstPtr &type) c
}
QQmlJSCodeGenerator::QQmlJSCodeGenerator(const QV4::Compiler::Context *compilerContext,
- const QV4::Compiler::JSUnitGenerator *unitGenerator,
- const QQmlJSTypeResolver *typeResolver,
- QQmlJSLogger *logger, const QStringList &sourceCodeLines)
- : QQmlJSCompilePass(unitGenerator, typeResolver, logger)
- , m_sourceCodeLines(sourceCodeLines)
+ const QV4::Compiler::JSUnitGenerator *unitGenerator,
+ const QQmlJSTypeResolver *typeResolver,
+ QQmlJSLogger *logger, BasicBlocks basicBlocks,
+ InstructionAnnotations annotations)
+ : QQmlJSCompilePass(unitGenerator, typeResolver, logger, basicBlocks, annotations)
, m_context(compilerContext)
{}
@@ -74,54 +73,100 @@ QString QQmlJSCodeGenerator::metaTypeFromName(const QQmlJSScope::ConstPtr &type)
+ u"\"); return t; }()"_s;
}
+QString QQmlJSCodeGenerator::compositeListMetaType(const QString &elementName) const
+{
+ return u"[](auto *aotContext) { static const auto t = QQmlPrivate::compositeListMetaType("
+ "aotContext->compilationUnit, QStringLiteral(\""_s
+ + elementName
+ + u"\")); return t; }(aotContext)"_s;
+}
+
+QString QQmlJSCodeGenerator::compositeMetaType(const QString &elementName) const
+{
+ return u"[](auto *aotContext) { static const auto t = QQmlPrivate::compositeMetaType("
+ "aotContext->compilationUnit, QStringLiteral(\""_s
+ + elementName
+ + u"\")); return t; }(aotContext)"_s;
+}
+
QString QQmlJSCodeGenerator::metaObject(const QQmlJSScope::ConstPtr &objectType)
{
- if (!objectType->isComposite()) {
- if (objectType->internalName() == u"QObject"_s
- || objectType->internalName() == u"QQmlComponent"_s) {
- return u'&' + objectType->internalName() + u"::staticMetaObject"_s;
+ if (objectType->isComposite()) {
+ const QString name = m_typeResolver->nameForType(objectType);
+ if (name.isEmpty()) {
+ reject(u"retrieving the metaObject of a composite type without an element name."_s);
+ return QString();
}
- return metaTypeFromName(objectType) + u".metaObject()"_s;
+ return compositeMetaType(name) + u".metaObject()"_s;
}
- reject(u"retrieving the metaObject of a composite type without using an instance."_s);
- return QString();
+ if (objectType->internalName() == u"QObject"_s
+ || objectType->internalName() == u"QQmlComponent"_s) {
+ return u'&' + objectType->internalName() + u"::staticMetaObject"_s;
+ }
+ return metaTypeFromName(objectType) + u".metaObject()"_s;
}
-QQmlJSAotFunction QQmlJSCodeGenerator::run(
- const Function *function, const InstructionAnnotations *annotations,
- QQmlJS::DiagnosticMessage *error)
+QString QQmlJSCodeGenerator::metaType(const QQmlJSScope::ConstPtr &type)
+{
+ if (type->isComposite()) {
+ const QString name = m_typeResolver->nameForType(type);
+ if (!name.isEmpty())
+ return compositeMetaType(name);
+ }
+
+ if (type->isListProperty() && type->valueType()->isComposite()) {
+ const QString name = m_typeResolver->nameForType(type->valueType());
+ if (!name.isEmpty())
+ return compositeListMetaType(name);
+ }
+
+ return m_typeResolver->equals(m_typeResolver->genericType(type), type)
+ ? metaTypeFromType(type)
+ : metaTypeFromName(type);
+}
+
+QQmlJSAotFunction QQmlJSCodeGenerator::run(const Function *function,
+ QQmlJS::DiagnosticMessage *error,
+ bool basicBlocksValidationFailed)
{
- m_annotations = annotations;
m_function = function;
m_error = error;
- QHash<int, QHash<QQmlJSScope::ConstPtr, QString>> registerNames;
+ QHash<int, int> numRegisterVariablesPerIndex;
- auto addVariable = [&](int registerIndex, const QQmlJSScope::ConstPtr &seenType) {
+ const auto addVariable
+ = [&](int registerIndex, int lookupIndex, const QQmlJSScope::ConstPtr &seenType) {
// Don't generate any variables for registers that are initialized with undefined.
if (registerIndex == InvalidRegister || !isTypeStorable(m_typeResolver, seenType))
return;
- auto &typesForRegisters = m_registerVariables[registerIndex];
- if (!typesForRegisters.contains(seenType)) {
- auto &currentRegisterNames = registerNames[registerIndex];
- QString &name = currentRegisterNames[m_typeResolver->comparableType(seenType)];
- if (name.isEmpty())
- name = u"r%1_%2"_s.arg(registerIndex).arg(currentRegisterNames.size());
- typesForRegisters[seenType] = name;
+ const RegisterVariablesKey key = { seenType->internalName(), registerIndex, lookupIndex };
+
+
+ const auto oldSize = m_registerVariables.size();
+ auto &e = m_registerVariables[key];
+ if (m_registerVariables.size() != oldSize) {
+ e.variableName = u"r%1_%2"_s
+ .arg(registerIndex)
+ .arg(numRegisterVariablesPerIndex[registerIndex]++);
+ e.storedType = seenType;
}
+ ++e.numTracked;
};
QT_WARNING_PUSH
QT_WARNING_DISABLE_CLANG("-Wrange-loop-analysis")
- for (const auto &annotation : *m_annotations) {
+ for (const auto &annotation : m_annotations) {
addVariable(annotation.second.changedRegisterIndex,
+ annotation.second.changedRegister.resultLookupIndex(),
annotation.second.changedRegister.storedType());
for (auto it = annotation.second.typeConversions.begin(),
end = annotation.second.typeConversions.end();
it != end; ++it) {
- addVariable(it.key(), it.value().content.storedType());
+ addVariable(
+ it.key(), it.value().content.resultLookupIndex(),
+ it.value().content.storedType());
}
}
QT_WARNING_POP
@@ -141,144 +186,205 @@ QT_WARNING_POP
QQmlJSAotFunction result;
result.includes.swap(m_includes);
- QDuplicateTracker<QString> generatedVariables;
+ if (basicBlocksValidationFailed) {
+ result.code += "// QV4_BASIC_BLOCK_VALIDATION_FAILED: This file failed compilation "_L1
+ "with basic blocks validation but compiled without it.\n"_L1;
+ }
+
+ result.code += u"// %1 at line %2, column %3\n"_s
+ .arg(m_context->name).arg(m_context->line).arg(m_context->column);
+
for (auto registerIt = m_registerVariables.cbegin(), registerEnd = m_registerVariables.cend();
registerIt != registerEnd; ++registerIt) {
- const auto &registerTypes = *registerIt;
- const int registerIndex = registerIt.key();
+ const int registerIndex = registerIt.key().registerIndex;
const bool registerIsArgument = isArgument(registerIndex);
- for (auto registerTypeIt = registerTypes.constBegin(), end = registerTypes.constEnd();
- registerTypeIt != end; ++registerTypeIt) {
+ result.code += registerIt.key().internalName;
- const QQmlJSScope::ConstPtr storedType = registerTypeIt.key();
+ const QQmlJSScope::ConstPtr storedType = registerIt->storedType;
+ const bool isPointer
+ = (storedType->accessSemantics() == QQmlJSScope::AccessSemantics::Reference);
+ if (isPointer)
+ result.code += u" *"_s;
+ else
+ result.code += u' ';
+
+ if (!registerIsArgument
+ && registerIndex != Accumulator
+ && registerIndex != This
+ && !m_typeResolver->registerContains(
+ function->registerTypes[registerIndex - firstRegisterIndex()],
+ m_typeResolver->voidType())) {
+ result.code += registerIt->variableName + u" = "_s;
+ result.code += convertStored(m_typeResolver->voidType(), storedType, QString());
+ } else if (registerIsArgument && registerIsStoredIn(
+ argumentType(registerIndex), storedType)) {
+ const int argumentIndex = registerIndex - FirstArgument;
+ const QQmlJSRegisterContent argument
+ = m_function->argumentTypes[argumentIndex];
+ const QQmlJSRegisterContent originalArgument = original(argument);
+
+ const bool needsConversion = argument != originalArgument;
+ if (!isPointer && registerIt->numTracked == 1 && !needsConversion) {
+ // Not a pointer, never written to, and doesn't need any initial conversion.
+ // This is a readonly argument.
+ //
+ // We would like to make the variable a const ref if it's a readonly argument,
+ // but due to the various call interfaces accepting non-const values, we can't.
+ // We rely on those calls to still not modify their arguments in place.
+ result.code += u'&';
+ }
- if (generatedVariables.hasSeen(registerTypeIt.value()))
- continue;
+ result.code += registerIt->variableName + u" = "_s;
- result.code += storedType->internalName();
+ const QString originalValue
+ = u"(*static_cast<"_s + castTargetName(originalArgument.storedType())
+ + u"*>(argv["_s + QString::number(argumentIndex + 1) + u"]))"_s;
- const bool isPointer
- = (storedType->accessSemantics() == QQmlJSScope::AccessSemantics::Reference);
- if (isPointer)
- result.code += u" *"_s;
+ if (needsConversion)
+ result.code += conversion(originalArgument, argument, originalValue);
else
- result.code += u' ';
-
- if (!registerIsArgument && registerIndex != Accumulator
- && !m_typeResolver->registerIsStoredIn(
- function->registerTypes[registerIndex - firstRegisterIndex()],
- m_typeResolver->voidType())) {
- result.code += registerTypeIt.value() + u" = "_s;
- result.code += conversion(m_typeResolver->voidType(), storedType, QString());
- } else if (registerIsArgument && m_typeResolver->registerIsStoredIn(
- argumentType(registerIndex), storedType)) {
- const int argumentIndex = registerIndex - FirstArgument;
- const QQmlJSScope::ConstPtr argument
- = m_function->argumentTypes[argumentIndex].storedType();
- const QQmlJSScope::ConstPtr original
- = m_typeResolver->originalType(argument);
-
- const bool needsConversion = argument != original;
- if (!isPointer && registerTypes.size() == 1 && !needsConversion) {
- // Not a pointer, never written to, and doesn't need any initial conversion.
- // This is a readonly argument.
- //
- // We would like to make the variable a const ref if it's a readonly argument,
- // but due to the various call interfaces accepting non-const values, we can't.
- // We rely on those calls to still not modify their arguments in place.
- result.code += u'&';
- }
-
- result.code += registerTypeIt.value() + u" = "_s;
-
- const QString originalValue = u"*static_cast<"_s + castTargetName(original)
- + u"*>(argumentsPtr["_s + QString::number(argumentIndex) + u"])"_s;
-
- if (needsConversion)
- result.code += conversion(original, argument, originalValue);
- else
- result.code += originalValue;
- } else {
- result.code += registerTypeIt.value();
- }
- result.code += u";\n"_s;
+ result.code += originalValue;
+ } else {
+ result.code += registerIt->variableName;
}
+ result.code += u";\n"_s;
}
result.code += m_body;
- for (const QQmlJSRegisterContent &argType : std::as_const(function->argumentTypes)) {
- if (argType.isValid()) {
- result.argumentTypes.append(
- m_typeResolver->originalType(argType.storedType())
- ->augmentedInternalName());
- } else {
- result.argumentTypes.append(u"void"_s);
- }
- }
- if (function->returnType) {
- result.returnType = function->returnType->internalName();
- if (function->returnType->accessSemantics() == QQmlJSScope::AccessSemantics::Reference)
- result.returnType += u'*';
+ QString signature
+ = u" struct { QV4::ExecutableCompilationUnit *compilationUnit; } c { unit };\n"
+ " const auto *aotContext = &c;\n"
+ " Q_UNUSED(aotContext);\n"_s;
+
+ if (function->returnType.isValid()) {
+ signature += u" argTypes[0] = %1;\n"_s.arg(
+ metaType(function->returnType.containedType()));
} else {
- result.returnType = u"void"_s;
+ signature += u" argTypes[0] = QMetaType();\n"_s;
+ }
+ result.numArguments = function->argumentTypes.length();
+ for (qsizetype i = 0; i != result.numArguments; ++i) {
+ signature += u" argTypes[%1] = %2;\n"_s.arg(
+ QString::number(i + 1),
+ metaType(m_typeResolver->originalContainedType(function->argumentTypes[i])));
}
+ result.signature = signature;
return result;
}
-QString QQmlJSCodeGenerator::errorReturnValue()
+void QQmlJSCodeGenerator::generateReturnError()
{
- if (auto ret = m_function->returnType) {
- return ret->accessSemantics() == QQmlJSScope::AccessSemantics::Reference
- ? conversion(m_typeResolver->nullType(), ret, QString())
- : ret->internalName() + u"()"_s;
+ const auto finalizeReturn = qScopeGuard([this]() { m_body += u"return;\n"_s; });
+
+ m_body += u"aotContext->setReturnValueUndefined();\n"_s;
+ const auto ret = m_function->returnType;
+ if (!ret.isValid() || m_typeResolver->registerContains(ret, m_typeResolver->voidType()))
+ return;
+
+ m_body += u"if (argv[0]) {\n"_s;
+
+ const auto contained = ret.containedType();
+ const auto stored = ret.storedType();
+ if (contained->isReferenceType() && stored->isReferenceType()) {
+ m_body += u" *static_cast<"_s
+ + stored->augmentedInternalName()
+ + u" *>(argv[0]) = nullptr;\n"_s;
+ } else if (m_typeResolver->equals(contained, stored)) {
+ m_body += u" *static_cast<"_s + stored->internalName() + u" *>(argv[0]) = "_s
+ + stored->internalName() + u"();\n"_s;
+ } else {
+ m_body += u" const QMetaType returnType = "_s
+ + metaType(ret.containedType()) + u";\n"_s;
+ m_body += u" returnType.destruct(argv[0]);\n"_s;
+ m_body += u" returnType.construct(argv[0]);\n "_s;
}
- return QString();
+
+ m_body += u"}\n"_s;
}
void QQmlJSCodeGenerator::generate_Ret()
{
INJECT_TRACE_INFO(generate_Ret);
- if (m_function->returnType) {
- const QString signalUndefined = u"aotContext->setReturnValueUndefined();\n"_s;
- if (!m_state.accumulatorVariableIn.isEmpty()) {
- const QString in = m_state.accumulatorVariableIn;
- if (m_typeResolver->registerIsStoredIn(
- m_state.accumulatorIn(), m_typeResolver->varType())) {
- m_body += u"if (!"_s + in + u".isValid())\n"_s;
- m_body += u" "_s + signalUndefined;
- } else if (m_typeResolver->registerIsStoredIn(
- m_state.accumulatorIn(), m_typeResolver->jsPrimitiveType())) {
- m_body += u"if ("_s + in
- + u".type() == QJSPrimitiveValue::Undefined)\n"_s;
- m_body += u" "_s + signalUndefined;
- } else if (m_typeResolver->registerIsStoredIn(
- m_state.accumulatorIn(), m_typeResolver->jsValueType())) {
- m_body += u"if ("_s + in + u".isUndefined())\n"_s;
- m_body += u" "_s + signalUndefined;
- }
- m_body += u"return "_s
- + conversion(m_state.accumulatorIn().storedType(), m_function->returnType, in);
- } else {
- if (m_typeResolver->equals(m_state.accumulatorIn().storedType(),
- m_typeResolver->voidType())) {
- m_body += signalUndefined;
- }
- m_body += u"return "_s + conversion(
- m_state.accumulatorIn().storedType(), m_function->returnType, QString());
+ const auto finalizeReturn = qScopeGuard([this]() {
+ m_body += u"return;\n"_s;
+ m_skipUntilNextLabel = true;
+ resetState();
+ });
+
+ if (!m_function->returnType.isValid())
+ return;
+
+ m_body += u"if (argv[0]) {\n"_s;
+
+ const QString signalUndefined = u"aotContext->setReturnValueUndefined();\n"_s;
+ const QString in = m_state.accumulatorVariableIn;
+
+ if (in.isEmpty()) {
+ if (m_typeResolver->equals(m_state.accumulatorIn().storedType(),
+ m_typeResolver->voidType())) {
+ m_body += signalUndefined;
+
}
+ } else if (registerIsStoredIn(
+ m_state.accumulatorIn(), m_typeResolver->varType())) {
+ m_body += u" if (!"_s + in + u".isValid())\n"_s;
+ m_body += u" "_s + signalUndefined;
+ } else if (registerIsStoredIn(
+ m_state.accumulatorIn(), m_typeResolver->jsPrimitiveType())) {
+ m_body += u" if ("_s + in + u".type() == QJSPrimitiveValue::Undefined)\n"_s;
+ m_body += u" "_s + signalUndefined;
+ } else if (registerIsStoredIn(
+ m_state.accumulatorIn(), m_typeResolver->jsValueType())) {
+ m_body += u" if ("_s + in + u".isUndefined())\n"_s;
+ m_body += u" "_s + signalUndefined;
+ }
+
+ if (m_typeResolver->registerContains(
+ m_function->returnType, m_typeResolver->voidType())) {
+ m_body += u"}\n"_s;
+ return;
+ }
+
+ const auto contained = m_function->returnType.containedType();
+ const auto stored = m_function->returnType.storedType();
+ if (m_typeResolver->equals(contained, stored)
+ || (contained->isReferenceType() && stored->isReferenceType())) {
+ // We can always std::move here, no matter what the optimization pass has detected. The
+ // function returns and nothing can access the accumulator register anymore afterwards.
+ m_body += u" *static_cast<"_s
+ + stored->augmentedInternalName()
+ + u" *>(argv[0]) = "_s
+ + conversion(
+ m_state.accumulatorIn(), m_function->returnType,
+ m_typeResolver->isTriviallyCopyable(m_state.accumulatorIn().storedType())
+ ? in
+ : u"std::move("_s + in + u')')
+ + u";\n"_s;
+ } else if (m_typeResolver->registerContains(m_state.accumulatorIn(), contained)) {
+ m_body += u" const QMetaType returnType = "_s + contentType(m_state.accumulatorIn(), in)
+ + u";\n"_s;
+ m_body += u" returnType.destruct(argv[0]);\n"_s;
+ m_body += u" returnType.construct(argv[0], "_s
+ + contentPointer(m_state.accumulatorIn(), in) + u");\n"_s;
} else {
- m_body += u"return"_s;
+ m_body += u" const auto converted = "_s
+ + conversion(m_state.accumulatorIn(), m_function->returnType,
+ consumedAccumulatorVariableIn()) + u";\n"_s;
+ m_body += u" const QMetaType returnType = "_s
+ + contentType(m_function->returnType, u"converted"_s)
+ + u";\n"_s;
+ m_body += u" returnType.destruct(argv[0]);\n"_s;
+ m_body += u" returnType.construct(argv[0], "_s
+ + contentPointer(m_function->returnType, u"converted"_s) + u");\n"_s;
}
- m_body += u";\n"_s;
- m_skipUntilNextLabel = true;
- resetState();
+ m_body += u"}\n"_s;
}
void QQmlJSCodeGenerator::generate_Debug()
@@ -325,30 +431,29 @@ void QQmlJSCodeGenerator::generate_LoadConst(int index)
m_body += m_state.accumulatorVariableOut + u" = "_s;
if (type == m_typeResolver->realType()) {
m_body += conversion(
- type, m_state.accumulatorOut().storedType(),
+ type, m_state.accumulatorOut(),
toNumericString(value.doubleValue()));
- } else if (type == m_typeResolver->intType()) {
+ } else if (type == m_typeResolver->int32Type()) {
m_body += conversion(
- type, m_state.accumulatorOut().storedType(),
+ type, m_state.accumulatorOut(),
QString::number(value.integerValue()));
} else if (type == m_typeResolver->boolType()) {
m_body += conversion(
- type, m_state.accumulatorOut().storedType(),
+ type, m_state.accumulatorOut(),
value.booleanValue() ? u"true"_s : u"false"_s);
} else if (type == m_typeResolver->voidType()) {
m_body += conversion(
- type, m_state.accumulatorOut().storedType(),
+ type, m_state.accumulatorOut(),
QString());
} else if (type == m_typeResolver->nullType()) {
m_body += conversion(
- type, m_state.accumulatorOut().storedType(),
+ type, m_state.accumulatorOut(),
u"nullptr"_s);
} else {
reject(u"unsupported constant type"_s);
}
m_body += u";\n"_s;
- generateOutputVariantConversion(type);
}
void QQmlJSCodeGenerator::generate_LoadZero()
@@ -357,9 +462,8 @@ void QQmlJSCodeGenerator::generate_LoadZero()
m_body += m_state.accumulatorVariableOut;
m_body += u" = "_s + conversion(
- m_typeResolver->intType(), m_state.accumulatorOut().storedType(), u"0"_s);
+ m_typeResolver->int32Type(), m_state.accumulatorOut(), u"0"_s);
m_body += u";\n"_s;
- generateOutputVariantConversion(m_typeResolver->intType());
}
void QQmlJSCodeGenerator::generate_LoadTrue()
@@ -368,9 +472,8 @@ void QQmlJSCodeGenerator::generate_LoadTrue()
m_body += m_state.accumulatorVariableOut;
m_body += u" = "_s + conversion(
- m_typeResolver->boolType(), m_state.accumulatorOut().storedType(), u"true"_s);
+ m_typeResolver->boolType(), m_state.accumulatorOut(), u"true"_s);
m_body += u";\n"_s;
- generateOutputVariantConversion(m_typeResolver->boolType());
}
void QQmlJSCodeGenerator::generate_LoadFalse()
@@ -379,9 +482,8 @@ void QQmlJSCodeGenerator::generate_LoadFalse()
m_body += m_state.accumulatorVariableOut;
m_body += u" = "_s + conversion(
- m_typeResolver->boolType(), m_state.accumulatorOut().storedType(), u"false"_s);
+ m_typeResolver->boolType(), m_state.accumulatorOut(), u"false"_s);
m_body += u";\n"_s;
- generateOutputVariantConversion(m_typeResolver->boolType());
}
void QQmlJSCodeGenerator::generate_LoadNull()
@@ -389,10 +491,9 @@ void QQmlJSCodeGenerator::generate_LoadNull()
INJECT_TRACE_INFO(generate_LoadNull);
m_body += m_state.accumulatorVariableOut + u" = "_s;
- m_body += conversion(m_typeResolver->nullType(), m_state.accumulatorOut().storedType(),
+ m_body += conversion(m_typeResolver->nullType(), m_state.accumulatorOut(),
u"nullptr"_s);
m_body += u";\n"_s;
- generateOutputVariantConversion(m_typeResolver->nullType());
}
void QQmlJSCodeGenerator::generate_LoadUndefined()
@@ -400,10 +501,9 @@ void QQmlJSCodeGenerator::generate_LoadUndefined()
INJECT_TRACE_INFO(generate_LoadUndefined);
m_body += m_state.accumulatorVariableOut + u" = "_s;
- m_body += conversion(m_typeResolver->voidType(), m_state.accumulatorOut().storedType(),
+ m_body += conversion(m_typeResolver->voidType(), m_state.accumulatorOut(),
QString());
m_body += u";\n"_s;
- generateOutputVariantConversion(m_typeResolver->voidType());
}
void QQmlJSCodeGenerator::generate_LoadInt(int value)
@@ -412,10 +512,9 @@ void QQmlJSCodeGenerator::generate_LoadInt(int value)
m_body += m_state.accumulatorVariableOut;
m_body += u" = "_s;
- m_body += conversion(m_typeResolver->intType(), m_state.accumulatorOut().storedType(),
+ m_body += conversion(m_typeResolver->int32Type(), m_state.accumulatorOut(),
QString::number(value));
m_body += u";\n"_s;
- generateOutputVariantConversion(m_typeResolver->intType());
}
void QQmlJSCodeGenerator::generate_MoveConst(int constIndex, int destTemp)
@@ -431,7 +530,7 @@ void QQmlJSCodeGenerator::generate_MoveConst(int constIndex, int destTemp)
const auto v4Value = QV4::StaticValue::fromReturnedValue(
m_jsUnitGenerator->constant(constIndex));
- const auto changed = m_state.changedRegister().storedType();
+ const auto changed = m_state.changedRegister();
QQmlJSScope::ConstPtr contained;
QString input;
@@ -444,16 +543,16 @@ void QQmlJSCodeGenerator::generate_MoveConst(int constIndex, int destTemp)
contained = m_typeResolver->boolType();
input = v4Value.booleanValue() ? u"true"_s : u"false"_s;
} else if (v4Value.isInteger()) {
- contained = m_typeResolver->intType();
+ contained = m_typeResolver->int32Type();
input = QString::number(v4Value.int_32());
} else if (v4Value.isDouble()) {
contained = m_typeResolver->realType();
input = toNumericString(v4Value.doubleValue());
} else {
reject(u"unknown const type"_s);
+ return;
}
m_body += conversion(contained, changed, input) + u";\n"_s;
- generateOutputVariantConversion(contained);
}
void QQmlJSCodeGenerator::generate_LoadReg(int reg)
@@ -536,11 +635,9 @@ void QQmlJSCodeGenerator::generate_LoadRuntimeString(int stringId)
m_body += m_state.accumulatorVariableOut;
m_body += u" = "_s;
- m_body += conversion(m_typeResolver->stringType(), m_state.accumulatorOut().storedType(),
+ m_body += conversion(m_typeResolver->stringType(), m_state.accumulatorOut(),
QQmlJSUtils::toLiteral(m_jsUnitGenerator->stringForIndex(stringId)));
m_body += u";\n"_s;
-
- generateOutputVariantConversion(m_typeResolver->stringType());
}
void QQmlJSCodeGenerator::generate_MoveRegExp(int regExpId, int destReg)
@@ -585,9 +682,11 @@ void QQmlJSCodeGenerator::generate_LoadQmlContextPropertyLookup(int index)
const int nameIndex = m_jsUnitGenerator->lookupNameIndex(index);
const QString name = m_jsUnitGenerator->stringForIndex(nameIndex);
if (m_state.accumulatorOut().variant() == QQmlJSRegisterContent::JavaScriptGlobal) {
+ // This produces a QJSValue. The QQmlJSMetaProperty used to analyze it may have more details
+ // but the QQmlJSAotContext API does not reflect them.
m_body += m_state.accumulatorVariableOut + u" = "_s
+ conversion(
- m_typeResolver->jsValueType(), m_state.accumulatorOut().storedType(),
+ m_typeResolver->jsValueType(), m_state.accumulatorOut(),
u"aotContext->javaScriptGlobalProperty("_s + QString::number(nameIndex) + u")")
+ u";\n"_s;
return;
@@ -633,9 +732,7 @@ void QQmlJSCodeGenerator::generate_StoreNameSloppy(int nameIndex)
INJECT_TRACE_INFO(generate_StoreNameSloppy);
const QString name = m_jsUnitGenerator->stringForIndex(nameIndex);
- const QQmlJSRegisterContent specific = m_typeResolver->scopedType(m_function->qmlScope, name);
- const QQmlJSRegisterContent type = specific.storedIn(
- m_typeResolver->genericType(specific.storedType()));
+ const QQmlJSRegisterContent type = m_typeResolver->scopedType(m_function->qmlScope, name);
Q_ASSERT(type.isProperty());
switch (type.variant()) {
@@ -672,37 +769,54 @@ void QQmlJSCodeGenerator::generate_LoadElement(int base)
const QQmlJSRegisterContent baseType = registerType(base);
- if (!m_typeResolver->isNumeric(m_state.accumulatorIn())
- || (!baseType.isList()
- && !m_typeResolver->registerIsStoredIn(baseType, m_typeResolver->stringType()))) {
- reject(u"LoadElement with non-list base type or non-numeric arguments"_s);
+ if (!baseType.isList()
+ && !registerIsStoredIn(baseType, m_typeResolver->stringType())) {
+ reject(u"LoadElement with non-list base type "_s + baseType.descriptiveName());
return;
}
- AccumulatorConverter registers(this);
-
- const QString baseName = registerVariable(base);
- const QString indexName = m_state.accumulatorVariableIn;
-
const QString voidAssignment = u" "_s + m_state.accumulatorVariableOut + u" = "_s +
- conversion(m_typeResolver->globalType(m_typeResolver->voidType()),
+ conversion(global(m_typeResolver->voidType()),
m_state.accumulatorOut(), QString()) + u";\n"_s;
- if (!m_typeResolver->isIntegral(m_state.accumulatorIn())) {
- m_body += u"if (!QJSNumberCoercion::isInteger("_s + indexName + u"))\n"_s
+ QString indexName = m_state.accumulatorVariableIn;
+ QQmlJSScope::ConstPtr indexType;
+ if (m_typeResolver->isNumeric(m_state.accumulatorIn())) {
+ indexType = m_state.accumulatorIn().containedType();
+ } else if (m_state.accumulatorIn().isConversion()) {
+ const auto target = m_typeResolver->extractNonVoidFromOptionalType(m_state.accumulatorIn());
+ if (m_typeResolver->isNumeric(target)) {
+ indexType = target;
+ m_body += u"if (!" + indexName + u".metaType().isValid())\n"
+ + voidAssignment
+ + u"else ";
+ indexName = convertStored(
+ m_state.accumulatorIn().storedType(), indexType, indexName);
+ } else {
+ reject(u"LoadElement with non-numeric argument"_s);
+ return;
+ }
+ }
+
+ AccumulatorConverter registers(this);
+ const QString baseName = registerVariable(base);
+
+ if (!m_typeResolver->isNativeArrayIndex(indexType)) {
+ m_body += u"if (!QJSNumberCoercion::isArrayIndex("_s + indexName + u"))\n"_s
+ + voidAssignment
+ + u"else "_s;
+ } else if (!m_typeResolver->isUnsignedInteger(indexType)) {
+ m_body += u"if ("_s + indexName + u" < 0)\n"_s
+ voidAssignment
+ u"else "_s;
}
- if (m_typeResolver->registerIsStoredIn(baseType, m_typeResolver->listPropertyType())) {
+ if (registerIsStoredIn(baseType, m_typeResolver->listPropertyType())) {
// Our QQmlListProperty only keeps plain QObject*.
- const auto valueType = m_typeResolver->valueType(baseType);
- const auto elementType = m_typeResolver->globalType(
- m_typeResolver->genericType(m_typeResolver->containedType(valueType)));
+ const auto elementType = global(m_typeResolver->qObjectType());
- m_body += u"if ("_s + indexName + u" >= 0 && "_s + indexName
- + u" < "_s + baseName + u".count(&"_s + baseName
- + u"))\n"_s;
+ m_body += u"if ("_s + indexName + u" < "_s + baseName
+ + u".count(&"_s + baseName + u"))\n"_s;
m_body += u" "_s + m_state.accumulatorVariableOut + u" = "_s +
conversion(elementType, m_state.accumulatorOut(),
baseName + u".at(&"_s + baseName + u", "_s
@@ -712,18 +826,21 @@ void QQmlJSCodeGenerator::generate_LoadElement(int base)
return;
}
- const auto elementType = m_typeResolver->valueType(baseType);
+ // Since we can do .at() below, we know that we can natively store the element type.
+ QQmlJSRegisterContent elementType = m_typeResolver->valueType(baseType);
+ elementType = elementType.storedIn(m_typeResolver->storedType(elementType.containedType()));
QString access = baseName + u".at("_s + indexName + u')';
// TODO: Once we get a char type in QML, use it here.
- if (m_typeResolver->registerIsStoredIn(baseType, m_typeResolver->stringType()))
+ if (registerIsStoredIn(baseType, m_typeResolver->stringType()))
access = u"QString("_s + access + u")"_s;
- else if (!m_typeResolver->canUseValueTypes())
- reject(u"LoadElement in sequence type reference"_s);
+ else if (m_state.isRegisterAffectedBySideEffects(base))
+ reject(u"LoadElement on a sequence potentially affected by side effects"_s);
+ else if (baseType.storedType()->accessSemantics() != QQmlJSScope::AccessSemantics::Sequence)
+ reject(u"LoadElement on a sequence wrapped in a non-sequence type"_s);
- m_body += u"if ("_s + indexName + u" >= 0 && "_s + indexName
- + u" < "_s + baseName + u".size())\n"_s;
+ m_body += u"if ("_s + indexName + u" < "_s + baseName + u".size())\n"_s;
m_body += u" "_s + m_state.accumulatorVariableOut + u" = "_s +
conversion(elementType, m_state.accumulatorOut(), access) + u";\n"_s;
m_body += u"else\n"_s
@@ -735,18 +852,15 @@ void QQmlJSCodeGenerator::generate_StoreElement(int base, int index)
INJECT_TRACE_INFO(generate_StoreElement);
const QQmlJSRegisterContent baseType = registerType(base);
- const QQmlJSRegisterContent indexType = registerType(index);
+ const QQmlJSScope::ConstPtr indexType = registerType(index).containedType();
- if (!m_typeResolver->isNumeric(registerType(index)) || !baseType.isList()) {
+ if (!m_typeResolver->isNumeric(indexType) || !baseType.isList()) {
reject(u"StoreElement with non-list base type or non-numeric arguments"_s);
return;
}
- if (!m_typeResolver->registerIsStoredIn(baseType, m_typeResolver->listPropertyType())) {
- if (m_typeResolver->canUseValueTypes())
- reject(u"indirect StoreElement"_s);
- else
- reject(u"StoreElement in sequence type reference"_s);
+ if (baseType.storedType()->accessSemantics() != QQmlJSScope::AccessSemantics::Sequence) {
+ reject(u"indirect StoreElement"_s);
return;
}
@@ -754,19 +868,40 @@ void QQmlJSCodeGenerator::generate_StoreElement(int base, int index)
const QString indexName = registerVariable(index);
const auto valueType = m_typeResolver->valueType(baseType);
- const auto elementType = m_typeResolver->globalType(m_typeResolver->genericType(
- m_typeResolver->containedType(valueType)));
+ const auto elementType = global(m_typeResolver->genericType(
+ valueType.containedType()));
+
+ addInclude(u"QtQml/qjslist.h"_s);
+ if (!m_typeResolver->isNativeArrayIndex(indexType))
+ m_body += u"if (QJSNumberCoercion::isArrayIndex("_s + indexName + u")) {\n"_s;
+ else if (!m_typeResolver->isUnsignedInteger(indexType))
+ m_body += u"if ("_s + indexName + u" >= 0) {\n"_s;
+ else
+ m_body += u"{\n"_s;
- m_body += u"if ("_s;
- if (!m_typeResolver->isIntegral(indexType))
- m_body += u"QJSNumberCoercion::isInteger("_s + indexName + u") && "_s;
- m_body += indexName + u" >= 0 && "_s
- + indexName + u" < "_s + baseName + u".count(&"_s + baseName
- + u"))\n"_s;
- m_body += u" "_s + baseName + u".replace(&"_s + baseName
- + u", "_s + indexName + u", "_s;
+ if (registerIsStoredIn(baseType, m_typeResolver->listPropertyType())) {
+ m_body += u" if ("_s + indexName + u" < "_s + baseName + u".count(&"_s + baseName
+ + u"))\n"_s;
+ m_body += u" "_s + baseName + u".replace(&"_s + baseName
+ + u", "_s + indexName + u", "_s;
+ m_body += conversion(m_state.accumulatorIn(), elementType, m_state.accumulatorVariableIn)
+ + u");\n"_s;
+ m_body += u"}\n"_s;
+ return;
+ }
+
+ if (m_state.isRegisterAffectedBySideEffects(base))
+ reject(u"LoadElement on a sequence potentially affected by side effects"_s);
+
+ m_body += u" if ("_s + indexName + u" >= " + baseName + u".size())\n"_s;
+ m_body += u" QJSList(&"_s + baseName + u", aotContext->engine).resize("_s
+ + indexName + u" + 1);\n"_s;
+ m_body += u" "_s + baseName + u'[' + indexName + u"] = "_s;
m_body += conversion(m_state.accumulatorIn(), elementType, m_state.accumulatorVariableIn)
- + u");\n"_s;
+ + u";\n"_s;
+ m_body += u"}\n"_s;
+
+ generateWriteBack(base);
}
void QQmlJSCodeGenerator::generate_LoadProperty(int nameIndex)
@@ -786,9 +921,15 @@ void QQmlJSCodeGenerator::generateEnumLookup(int index)
{
const QString enumMember = m_state.accumulatorOut().enumMember();
- // If we're referring to the type, there's nothing to do.
- if (enumMember.isEmpty())
+ if (enumMember.isEmpty()) {
+ // If we're referring to the type, there's nothing to do.
+ // However, we should not get here since no one can ever use the enum metatype.
+ // The lookup is dead code and should be optimized away.
+ // ... unless you are actually trying to store the metatype itself in a property.
+ // We cannot compile such code.
+ reject(u"Lookup of enum metatype"_s);
return;
+ }
// If the metaenum has the value, just use it and skip all the rest.
const QQmlJSMetaEnum metaEnum = m_state.accumulatorOut().enumeration();
@@ -805,6 +946,12 @@ void QQmlJSCodeGenerator::generateEnumLookup(int index)
Q_ASSERT(!scopeType->isComposite());
const QString enumName = metaEnum.isFlag() ? metaEnum.alias() : metaEnum.name();
+ if (enumName.isEmpty()) {
+ if (metaEnum.isFlag() && !metaEnum.name().isEmpty())
+ reject(u"qmltypes misses name entry for flag; did you pass the enum type to Q_FLAG instead of the QFlag type?"
+ "\nType is %1, enum name is %2"_s.arg(scopeType->internalName(), metaEnum.name()));
+ reject(u"qmltypes misses name entry for enum"_s);
+ }
const QString lookup = u"aotContext->getEnumLookup("_s + QString::number(index)
+ u", &"_s + m_state.accumulatorVariableOut + u')';
const QString initialization = u"aotContext->initGetEnumLookup("_s
@@ -848,7 +995,7 @@ void QQmlJSCodeGenerator::generateTypeLookup(int index)
reject(u"script lookup"_s);
break;
case QQmlJSRegisterContent::MetaType: {
- if (!m_typeResolver->registerIsStoredIn(
+ if (!registerIsStoredIn(
m_state.accumulatorOut(), m_typeResolver->metaObjectType())) {
// TODO: Can we trigger this somehow?
// It might be impossible, but we better be safe here.
@@ -866,30 +1013,10 @@ void QQmlJSCodeGenerator::generateTypeLookup(int index)
}
}
-void QQmlJSCodeGenerator::generateOutputVariantConversion(const QQmlJSScope::ConstPtr &containedType)
-{
- if (changedRegisterVariable().isEmpty())
- return;
-
- const QQmlJSRegisterContent changed = m_state.changedRegister();
- if (!m_typeResolver->equals(changed.storedType(), m_typeResolver->varType()))
- return;
-
- const QQmlJSScope::ConstPtr target = m_typeResolver->containedType(changed);
- if (m_typeResolver->equals(target, containedType)
- || m_typeResolver->equals(target, m_typeResolver->varType())) {
- return;
- }
-
- // If we could store the type directly, we would not wrap it in a QVariant.
- // Therefore, our best bet here is metaTypeFromName().
- m_body += changedRegisterVariable() + u".convert("_s + metaTypeFromName(target) + u");\n"_s;
-}
-
void QQmlJSCodeGenerator::generateVariantEqualityComparison(
const QQmlJSRegisterContent &nonStorableContent, const QString &registerName, bool invert)
{
- const auto nonStorableType = m_typeResolver->containedType(nonStorableContent);
+ const auto nonStorableType = nonStorableContent.containedType();
QQmlJSScope::ConstPtr comparedType =
m_typeResolver->equals(nonStorableType, m_typeResolver->nullType())
? m_typeResolver->nullType()
@@ -899,7 +1026,7 @@ void QQmlJSCodeGenerator::generateVariantEqualityComparison(
m_body += u"if ("_s + registerName
+ u".metaType() == QMetaType::fromType<QJSPrimitiveValue>()) {\n"_s
+ m_state.accumulatorVariableOut + u" = "_s
- + conversion(m_typeResolver->boolType(), m_state.accumulatorOut().storedType(),
+ + conversion(m_typeResolver->boolType(), m_state.accumulatorOut(),
u"static_cast<const QJSPrimitiveValue *>("_s + registerName
+ u".constData())"_s + u"->type() "_s
+ (invert ? u"!="_s : u"=="_s)
@@ -909,7 +1036,7 @@ void QQmlJSCodeGenerator::generateVariantEqualityComparison(
+ u";\n} else if ("_s + registerName
+ u".metaType() == QMetaType::fromType<QJSValue>()) {\n"_s
+ m_state.accumulatorVariableOut + u" = "_s
- + conversion(m_typeResolver->boolType(), m_state.accumulatorOut().storedType(),
+ + conversion(m_typeResolver->boolType(), m_state.accumulatorOut(),
(invert ? u"!"_s : QString()) + u"static_cast<const QJSValue *>("_s
+ registerName + u".constData())"_s + u"->"_s
+ (m_typeResolver->equals(comparedType, m_typeResolver->nullType())
@@ -923,21 +1050,178 @@ void QQmlJSCodeGenerator::generateVariantEqualityComparison(
m_body += u"else if ("_s + registerName
+ u".metaType().flags().testFlag(QMetaType::PointerToQObject)) {\n"_s
+ m_state.accumulatorVariableOut + u" = "_s
- + conversion(m_typeResolver->boolType(), m_state.accumulatorOut().storedType(),
+ + conversion(m_typeResolver->boolType(), m_state.accumulatorOut(),
u"*static_cast<QObject *const *>("_s + registerName
+ u".constData())"_s + (invert ? u"!="_s : u"=="_s)
+ u" nullptr"_s)
+ u";\n} else if ("_s + registerName
+ u".metaType() == QMetaType::fromType<std::nullptr_t>()) {\n"_s
- + m_state.accumulatorVariableOut + u" = "_s + (invert ? u"false"_s : u"true"_s)
+ + m_state.accumulatorVariableOut + u" = "_s
+ + conversion(m_typeResolver->boolType(), m_state.accumulatorOut(),
+ (invert ? u"false"_s : u"true"_s))
+ u";\n}\n"_s;
}
// fallback case (if variant contains a different type, then it is not null or undefined)
m_body += u"else {\n"_s + m_state.accumulatorVariableOut + u" = "_s
- + (invert ? (registerName + u".isValid() ? true : false"_s)
- : (registerName + u".isValid() ? false : true"_s))
- + u";}\n"_s;
+ + conversion(m_typeResolver->boolType(), m_state.accumulatorOut(),
+ (invert ? (registerName + u".isValid() ? true : false"_s)
+ : (registerName + u".isValid() ? false : true"_s)))
+ + u";\n}"_s;
+}
+
+void QQmlJSCodeGenerator::generateVariantEqualityComparison(
+ const QQmlJSRegisterContent &storableContent, const QString &typedRegisterName,
+ const QString &varRegisterName, bool invert)
+{
+ // enumerations are ===-equal to their underlying type and they are stored as such.
+ // Therefore, use the underlying type right away.
+ const auto contained = storableContent.isEnumeration()
+ ? storableContent.storedType()
+ : storableContent.containedType();
+
+ if (contained->isReferenceType()) {
+ const QQmlJSRegisterContent comparable = builtin(m_typeResolver->qObjectType());
+ m_body += m_state.accumulatorVariableOut + u" = "_s + (invert ? u"!"_s : QString()) + u"(("
+ + varRegisterName + u".metaType().flags() & QMetaType::PointerToQObject) "_s
+ + u" && "_s + conversion(storableContent, comparable, typedRegisterName) + u" == "_s
+ + conversion(m_typeResolver->varType(), comparable, varRegisterName) + u");\n";
+ return;
+ }
+
+ if (m_typeResolver->isPrimitive(contained)) {
+ const QQmlJSRegisterContent comparable = builtin(m_typeResolver->jsPrimitiveType());
+ m_body += m_state.accumulatorVariableOut + u" = "_s + (invert ? u"!"_s : QString())
+ + conversion(storableContent, comparable, typedRegisterName)
+ + u".strictlyEquals("_s
+ + conversion(m_typeResolver->varType(), comparable, varRegisterName) + u");\n"_s;
+ return;
+ }
+
+ reject(u"comparison of non-primitive, non-object type to var"_s);
+}
+
+void QQmlJSCodeGenerator::generateArrayInitializer(int argc, int argv)
+{
+ const QQmlJSScope::ConstPtr stored = m_state.accumulatorOut().storedType();
+ const QQmlJSScope::ConstPtr value = stored->valueType();
+ Q_ASSERT(value);
+
+ QStringList initializer;
+ for (int i = 0; i < argc; ++i) {
+ initializer += convertStored(
+ registerType(argv + i).storedType(), value,
+ consumedRegisterVariable(argv + i));
+ }
+
+ m_body += m_state.accumulatorVariableOut + u" = "_s + stored->internalName() + u'{';
+ m_body += initializer.join(u", "_s);
+ m_body += u"};\n";
+}
+
+void QQmlJSCodeGenerator::generateWriteBack(int registerIndex)
+{
+ QString writeBackRegister = registerVariable(registerIndex);
+ bool writeBackAffectedBySideEffects = m_state.isRegisterAffectedBySideEffects(registerIndex);
+
+ for (QQmlJSRegisterContent writeBack = registerType(registerIndex);
+ !writeBack.storedType()->isReferenceType();) {
+ if (writeBackAffectedBySideEffects)
+ reject(u"write-back of value affected by side effects"_s);
+
+ if (writeBack.isConversion())
+ reject(u"write-back of converted value"_s);
+
+ const int lookupIndex = writeBack.resultLookupIndex();
+ if (lookupIndex == -1) {
+ // This is essential for the soundness of the type system.
+ //
+ // If a value or a list is returned from a function, we cannot know
+ // whether it is a copy or a reference. Therefore, we cannot know whether
+ // we have to write it back and so we have to reject any write on it.
+ //
+ // Only if we are sure that the value is locally created we can be sure
+ // we don't have to write it back. In this latter case we could allow
+ // a modification that doesn't write back.
+ reject(u"write-back of non-lookup"_s);
+ break;
+ }
+
+ const QString writeBackIndexString = QString::number(lookupIndex);
+
+ const QQmlJSRegisterContent::ContentVariant variant = writeBack.variant();
+ if (variant == QQmlJSRegisterContent::ScopeProperty
+ || variant == QQmlJSRegisterContent::ExtensionScopeProperty) {
+ const QString lookup = u"aotContext->writeBackScopeObjectPropertyLookup("_s
+ + writeBackIndexString
+ + u", "_s + contentPointer(writeBack, writeBackRegister) + u')';
+ const QString initialization
+ = u"aotContext->initLoadScopeObjectPropertyLookup("_s
+ + writeBackIndexString
+ + u", "_s + contentType(writeBack, writeBackRegister) + u')';
+ generateLookup(lookup, initialization);
+ break;
+ }
+
+
+ QQmlJSRegisterContent outerContent;
+ QString outerRegister;
+ bool outerAffectedBySideEffects = false;
+ for (auto it = m_state.lookups.constBegin(), end = m_state.lookups.constEnd();
+ it != end; ++it) {
+ if (it.value().content.resultLookupIndex() == writeBack.baseLookupIndex()) {
+ outerContent = it.value().content;
+ outerRegister = lookupVariable(outerContent.resultLookupIndex());
+ outerAffectedBySideEffects = it.value().affectedBySideEffects;
+ break;
+ }
+ }
+
+ if (!outerContent.isValid()) {
+ // If the lookup doesn't exist, it was killed by state merge.
+ reject(u"write-back of lookup across jumps or merges."_s);
+ break;
+ }
+
+ Q_ASSERT(!outerRegister.isEmpty());
+
+ switch (writeBack.variant()) {
+ case QQmlJSRegisterContent::ScopeProperty:
+ case QQmlJSRegisterContent::ExtensionScopeProperty:
+ Q_UNREACHABLE();
+ case QQmlJSRegisterContent::ObjectProperty:
+ case QQmlJSRegisterContent::ExtensionObjectProperty:
+ if (writeBack.scopeType()->isReferenceType()) {
+ const QString lookup = u"aotContext->writeBackObjectLookup("_s
+ + writeBackIndexString
+ + u", "_s + outerRegister
+ + u", "_s + contentPointer(writeBack, writeBackRegister) + u')';
+ const QString initialization = u"aotContext->initGetObjectLookup("_s
+ + writeBackIndexString
+ + u", "_s + outerRegister
+ + u", "_s + contentType(writeBack, writeBackRegister) + u')';
+ generateLookup(lookup, initialization);
+ } else {
+ const QString valuePointer = contentPointer(outerContent, outerRegister);
+ const QString lookup = u"aotContext->writeBackValueLookup("_s
+ + writeBackIndexString
+ + u", "_s + valuePointer
+ + u", "_s + contentPointer(writeBack, writeBackRegister) + u')';
+ const QString initialization = u"aotContext->initGetValueLookup("_s
+ + writeBackIndexString
+ + u", "_s + metaObject(writeBack.scopeType())
+ + u", "_s + contentType(writeBack, writeBackRegister) + u')';
+ generateLookup(lookup, initialization);
+ }
+ break;
+ default:
+ reject(u"SetLookup on value types (because of missing write-back)"_s);
+ }
+
+ writeBackRegister = outerRegister;
+ writeBack = outerContent;
+ writeBackAffectedBySideEffects = outerAffectedBySideEffects;
+ }
}
void QQmlJSCodeGenerator::rejectIfNonQObjectOut(const QString &error)
@@ -948,15 +1232,141 @@ void QQmlJSCodeGenerator::rejectIfNonQObjectOut(const QString &error)
}
}
+void QQmlJSCodeGenerator::rejectIfBadArray()
+{
+ const QQmlJSScope::ConstPtr stored = m_state.accumulatorOut().storedType();
+ if (stored->accessSemantics() != QQmlJSScope::AccessSemantics::Sequence) {
+ // This rejects any attempt to store the list into a QVariant.
+ // Therefore, we don't have to adjust the contained type below.
+
+ reject(u"storing an array in a non-sequence type"_s);
+ } else if (stored->isListProperty()) {
+ // We can, technically, generate code for this. But it's dangerous:
+ //
+ // const QString storage = m_state.accumulatorVariableOut + u"_storage"_s;
+ // m_body += stored->internalName() + u"::ListType " + storage
+ // + u" = {"_s + initializer.join(u", "_s) + u"};\n"_s;
+ // m_body += m_state.accumulatorVariableOut
+ // + u" = " + stored->internalName() + u"(nullptr, &"_s + storage + u");\n"_s;
+
+ reject(u"creating a QQmlListProperty not backed by a property"_s);
+
+ }
+}
+
+/*!
+ * \internal
+ *
+ * generates a check for the content pointer to be valid.
+ * Returns true if the content pointer needs to be retrieved from a QVariant, or
+ * false if the variable can be used as-is.
+ */
+bool QQmlJSCodeGenerator::generateContentPointerCheck(
+ const QQmlJSScope::ConstPtr &required, const QQmlJSRegisterContent &actual,
+ const QString &variable, const QString &errorMessage)
+{
+ const QQmlJSScope::ConstPtr scope = required;
+ const QQmlJSScope::ConstPtr input = actual.containedType();
+ if (QQmlJSUtils::searchBaseAndExtensionTypes(input,
+ [&](const QQmlJSScope::ConstPtr &base) {
+ return m_typeResolver->equals(base, scope);
+ })) {
+ return false;
+ }
+
+ if (!m_typeResolver->canHold(input, scope)) {
+ reject(u"lookup of members of %1 in %2"_s.arg(
+ scope->internalName(), input->internalName()));
+ }
+
+ bool needsVarContentConversion = false;
+ QString processedErrorMessage;
+ if (actual.storedType()->isReferenceType()) {
+ // Since we have verified the type in qqmljstypepropagator.cpp we now know
+ // that we can only have either null or the actual type here. Therefore,
+ // it's enough to check the pointer for null.
+ m_body += u"if ("_s + variable + u" == nullptr) {\n "_s;
+ processedErrorMessage = errorMessage.arg(u"null");
+ } else if (m_typeResolver->equals(actual.storedType(), m_typeResolver->varType())) {
+ // Since we have verified the type in qqmljstypepropagator.cpp we now know
+ // that we can only have either undefined or the actual type here. Therefore,
+ // it's enough to check the QVariant for isValid().
+ m_body += u"if (!"_s + variable + u".isValid()) {\n "_s;
+ needsVarContentConversion = true;
+ processedErrorMessage = errorMessage.arg(u"undefined");
+ } else {
+ reject(u"retrieving metatype from %1"_s.arg(actual.descriptiveName()));
+ }
+
+ generateSetInstructionPointer();
+ m_body += u" aotContext->engine->throwError(QJSValue::TypeError, "_s;
+ m_body += u"QLatin1String(\"%1\"));\n"_s.arg(processedErrorMessage);
+ generateReturnError();
+ m_body += u"}\n"_s;
+ return needsVarContentConversion;
+}
+
+QString QQmlJSCodeGenerator::resolveValueTypeContentPointer(
+ const QQmlJSScope::ConstPtr &required, const QQmlJSRegisterContent &actual,
+ const QString &variable, const QString &errorMessage)
+{
+ if (generateContentPointerCheck(required, actual, variable, errorMessage))
+ return variable + u".data()"_s;
+ return contentPointer(actual, variable);
+}
+
+QString QQmlJSCodeGenerator::resolveQObjectPointer(
+ const QQmlJSScope::ConstPtr &required, const QQmlJSRegisterContent &actual,
+ const QString &variable, const QString &errorMessage)
+{
+ if (generateContentPointerCheck(required, actual, variable, errorMessage))
+ return u"*static_cast<QObject *const *>("_s + variable + u".constData())"_s;
+ return variable;
+}
+
void QQmlJSCodeGenerator::generate_GetLookup(int index)
{
INJECT_TRACE_INFO(generate_GetLookup);
+ generate_GetLookupHelper(index);
+}
+void QQmlJSCodeGenerator::generate_GetLookupHelper(int index)
+{
if (m_state.accumulatorOut().isMethod()) {
reject(u"lookup of function property."_s);
return;
}
+ if (m_typeResolver->equals(m_state.accumulatorOut().scopeType(), m_typeResolver->mathObject())) {
+ QString name = m_jsUnitGenerator->lookupName(index);
+
+ double value{};
+ if (name == u"E") {
+ value = std::exp(1.0);
+ } else if (name == u"LN10") {
+ value = log(10.0);
+ } else if (name == u"LN2") {
+ value = log(2.0);
+ } else if (name == u"LOG10E") {
+ value = log10(std::exp(1.0));
+ } else if (name == u"LOG2E") {
+ value = log2(std::exp(1.0));
+ } else if (name == u"PI") {
+ value = 3.14159265358979323846;
+ } else if (name == u"SQRT1_2") {
+ value = std::sqrt(0.5);
+ } else if (name == u"SQRT2") {
+ value = std::sqrt(2.0);
+ } else {
+ Q_UNREACHABLE();
+ }
+
+ m_body += m_state.accumulatorVariableOut + u" = "_s
+ + conversion(m_typeResolver->realType(), m_state.accumulatorOut(), toNumericString(value))
+ + u";\n"_s;
+ return;
+ }
+
if (m_state.accumulatorOut().isImportNamespace()) {
Q_ASSERT(m_state.accumulatorOut().variant() == QQmlJSRegisterContent::ObjectModulePrefix);
// If we have an object module prefix, we need to pass through the original object.
@@ -981,8 +1391,8 @@ void QQmlJSCodeGenerator::generate_GetLookup(int index)
? QString::number(m_state.accumulatorIn().importNamespace())
: u"QQmlPrivate::AOTCompiledContext::InvalidStringId"_s;
const auto accumulatorIn = m_state.accumulatorIn();
- const bool isReferenceType = (accumulatorIn.storedType()->accessSemantics()
- == QQmlJSScope::AccessSemantics::Reference);
+ const QQmlJSScope::ConstPtr scope = m_state.accumulatorOut().scopeType();
+ const bool isReferenceType = scope->isReferenceType();
switch (m_state.accumulatorOut().variant()) {
case QQmlJSRegisterContent::ObjectAttached: {
@@ -993,6 +1403,13 @@ void QQmlJSCodeGenerator::generate_GetLookup(int index)
// that would be expensive.
reject(u"attached object for non-QObject type"_s);
}
+
+ if (!m_state.accumulatorIn().storedType()->isReferenceType()) {
+ // This can happen if we retroactively determine that the property might not be
+ // what we think it is (ie, it can be shadowed).
+ reject(u"attached object of potentially non-QObject base"_s);
+ }
+
rejectIfNonQObjectOut(u"non-QObject attached type"_s);
const QString lookup = u"aotContext->loadAttachedLookup("_s + indexString
@@ -1006,6 +1423,7 @@ void QQmlJSCodeGenerator::generate_GetLookup(int index)
}
case QQmlJSRegisterContent::ScopeAttached:
case QQmlJSRegisterContent::Singleton:
+ case QQmlJSRegisterContent::Script:
case QQmlJSRegisterContent::MetaType: {
generateTypeLookup(index);
return;
@@ -1016,60 +1434,105 @@ void QQmlJSCodeGenerator::generate_GetLookup(int index)
Q_ASSERT(m_state.accumulatorOut().isProperty());
- if (isReferenceType) {
+ if (registerIsStoredIn(accumulatorIn, m_typeResolver->jsValueType())) {
+ reject(u"lookup in QJSValue"_s);
+ } else if (isReferenceType) {
+ const QString inputPointer = resolveQObjectPointer(
+ scope, accumulatorIn, m_state.accumulatorVariableIn,
+ u"Cannot read property '%1' of %2"_s.arg(
+ m_jsUnitGenerator->lookupName(index)));
const QString lookup = u"aotContext->getObjectLookup("_s + indexString
- + u", "_s + m_state.accumulatorVariableIn + u", "_s
+ + u", "_s + inputPointer + u", "_s
+ contentPointer(m_state.accumulatorOut(), m_state.accumulatorVariableOut) + u')';
const QString initialization = u"aotContext->initGetObjectLookup("_s
- + indexString + u", "_s + m_state.accumulatorVariableIn
+ + indexString + u", "_s + inputPointer
+ u", "_s + contentType(m_state.accumulatorOut(), m_state.accumulatorVariableOut)
+ u')';
const QString preparation = getLookupPreparation(
m_state.accumulatorOut(), m_state.accumulatorVariableOut, index);
generateLookup(lookup, initialization, preparation);
- } else if (m_typeResolver->registerIsStoredIn(accumulatorIn, m_typeResolver->listPropertyType())
+ } else if ((scope->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence
+ || m_typeResolver->equals(scope, m_typeResolver->stringType()))
&& m_jsUnitGenerator->lookupName(index) == u"length"_s) {
+ const QQmlJSScope::ConstPtr stored = accumulatorIn.storedType();
+ if (stored->isListProperty()) {
+ m_body += m_state.accumulatorVariableOut + u" = "_s;
+ m_body += conversion(
+ global(m_typeResolver->sizeType()),
+ m_state.accumulatorOut(),
+ m_state.accumulatorVariableIn + u".count("_s + u'&'
+ + m_state.accumulatorVariableIn + u')');
+ m_body += u";\n"_s;
+ } else if (stored->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence
+ || m_typeResolver->equals(stored, m_typeResolver->stringType())) {
+ m_body += m_state.accumulatorVariableOut + u" = "_s
+ + conversion(global(m_typeResolver->sizeType()),
+ m_state.accumulatorOut(),
+ m_state.accumulatorVariableIn + u".length()"_s)
+ + u";\n"_s;
+ } else {
+ reject(u"access to 'length' property of sequence wrapped in non-sequence"_s);
+ }
+ } else if (registerIsStoredIn(accumulatorIn, m_typeResolver->variantMapType())) {
+ QString mapLookup = m_state.accumulatorVariableIn + u"["_s
+ + QQmlJSUtils::toLiteral(m_jsUnitGenerator->lookupName(index)) + u"]"_s;
m_body += m_state.accumulatorVariableOut + u" = "_s;
- m_body += conversion(
- m_typeResolver->globalType(m_typeResolver->intType()), m_state.accumulatorOut(),
- m_state.accumulatorVariableIn + u".count("_s + u'&'
- + m_state.accumulatorVariableIn + u')');
+ m_body += conversion(global(m_typeResolver->varType()),
+ m_state.accumulatorOut(), mapLookup);
m_body += u";\n"_s;
- } else if ((m_typeResolver->registerIsStoredIn(accumulatorIn, m_typeResolver->stringType())
- || accumulatorIn.storedType()->accessSemantics()
- == QQmlJSScope::AccessSemantics::Sequence)
- && m_jsUnitGenerator->lookupName(index) == u"length"_s) {
- m_body += m_state.accumulatorVariableOut + u" = "_s
- + conversion(m_typeResolver->globalType(m_typeResolver->intType()),
- m_state.accumulatorOut(),
- m_state.accumulatorVariableIn + u".length()"_s)
- + u";\n"_s;
- } else if (m_typeResolver->registerIsStoredIn(accumulatorIn, m_typeResolver->jsValueType())) {
- reject(u"lookup in QJSValue"_s);
- } else if (m_typeResolver->canUseValueTypes()) {
+ } else {
+ if (m_state.isRegisterAffectedBySideEffects(Accumulator))
+ reject(u"reading from a value that's potentially affected by side effects"_s);
+
+ const QString inputContentPointer = resolveValueTypeContentPointer(
+ scope, accumulatorIn, m_state.accumulatorVariableIn,
+ u"Cannot read property '%1' of %2"_s.arg(
+ m_jsUnitGenerator->lookupName(index)));
+
const QString lookup = u"aotContext->getValueLookup("_s + indexString
- + u", "_s + contentPointer(m_state.accumulatorIn(),
- m_state.accumulatorVariableIn)
- + u", "_s + contentPointer(m_state.accumulatorOut(),
- m_state.accumulatorVariableOut)
+ + u", "_s + inputContentPointer
+ + u", "_s + contentPointer(m_state.accumulatorOut(), m_state.accumulatorVariableOut)
+ u')';
const QString initialization = u"aotContext->initGetValueLookup("_s
+ indexString + u", "_s
- + metaObject(m_state.accumulatorOut().scopeType()) + u", "_s
+ + metaObject(scope) + u", "_s
+ contentType(m_state.accumulatorOut(), m_state.accumulatorVariableOut) + u')';
const QString preparation = getLookupPreparation(
m_state.accumulatorOut(), m_state.accumulatorVariableOut, index);
generateLookup(lookup, initialization, preparation);
- } else {
- reject(u"lookup in value type reference"_s);
}
}
void QQmlJSCodeGenerator::generate_GetOptionalLookup(int index, int offset)
{
- Q_UNUSED(index)
- Q_UNUSED(offset)
- BYTECODE_UNIMPLEMENTED();
+ INJECT_TRACE_INFO(generate_GetOptionalLookup);
+
+ const QQmlJSRegisterContent accumulatorIn = m_state.accumulatorIn();
+ QString accumulatorVarIn = m_state.accumulatorVariableIn;
+
+ const auto &annotation = m_annotations[currentInstructionOffset()];
+ if (accumulatorIn.storedType()->isReferenceType()) {
+ m_body += u"if (!%1)\n"_s.arg(accumulatorVarIn);
+ generateJumpCodeWithTypeConversions(offset);
+ } else if (m_typeResolver->equals(accumulatorIn.storedType(), m_typeResolver->varType())) {
+ m_body += u"if (!%1.isValid() || ((%1.metaType().flags() & QMetaType::PointerToQObject) "
+ "&& %1.value<QObject *>() == nullptr))\n"_s.arg(accumulatorVarIn);
+ generateJumpCodeWithTypeConversions(offset);
+ } else if (m_typeResolver->equals(accumulatorIn.storedType(), m_typeResolver->jsPrimitiveType())) {
+ m_body += u"if (%1.equals(QJSPrimitiveUndefined()) "
+ "|| %1.equals(QJSPrimitiveNull()))\n"_s.arg(accumulatorVarIn);
+ generateJumpCodeWithTypeConversions(offset);
+ } else if (annotation.changedRegisterIndex == Accumulator
+ && annotation.changedRegister.variant() == QQmlJSRegisterContent::ObjectEnum) {
+ // Nothing
+ } else if (m_typeResolver->equals(accumulatorIn.storedType(), m_typeResolver->jsValueType())) {
+ m_body += u"if (%1.isNull() || %1.isUndefined())\n"_s.arg(accumulatorVarIn);
+ generateJumpCodeWithTypeConversions(offset);
+ } else {
+ Q_UNREACHABLE(); // No other accumulatorIn stored types should be possible
+ }
+
+ generate_GetLookupHelper(index);
}
void QQmlJSCodeGenerator::generate_StoreProperty(int nameIndex, int baseReg)
@@ -1085,7 +1548,7 @@ QString QQmlJSCodeGenerator::setLookupPreparation(
if (m_typeResolver->registerContains(content, content.storedType()))
return QString();
- if (m_typeResolver->registerIsStoredIn(content, m_typeResolver->varType())) {
+ if (registerIsStoredIn(content, m_typeResolver->varType())) {
return u"const QMetaType argType = aotContext->lookupResultMetaType("_s
+ QString::number(lookup) + u");\n"_s
+ u"if (argType.isValid())\n "_s + arg + u".convert(argType)";
@@ -1101,18 +1564,33 @@ void QQmlJSCodeGenerator::generate_SetLookup(int index, int baseReg)
const QString indexString = QString::number(index);
const QQmlJSScope::ConstPtr valueType = m_state.accumulatorIn().storedType();
- const QQmlJSRegisterContent callBase = registerType(baseReg);
- const QQmlJSRegisterContent specific = m_typeResolver->memberType(
- callBase, m_jsUnitGenerator->lookupName(index));
+ const QQmlJSRegisterContent specific = m_state.readAccumulator();
+ Q_ASSERT(specific.isConversion());
+ const QQmlJSScope::ConstPtr originalScope
+ = m_typeResolver->originalType(specific.conversionResultScope());
+
if (specific.storedType().isNull()) {
reject(u"SetLookup. Could not find property "
- + callBase.storedType()->internalName()
- + u" on type "
- + m_jsUnitGenerator->lookupName(index));
+ + m_jsUnitGenerator->lookupName(index)
+ + u" on type "
+ + originalScope->internalName());
return;
}
- const QQmlJSRegisterContent property = specific.storedIn(
- m_typeResolver->genericType(specific.storedType()));
+
+ // Choose a container that can hold both, the "in" accumulator and what we actually want.
+ // If the types are all the same because we can all store them as verbatim C++ types,
+ // the container will also be that type.
+
+ QQmlJSRegisterContent property = specific;
+ if (!m_typeResolver->equals(specific.storedType(), valueType)) {
+ if (m_typeResolver->isPrimitive(specific.storedType())
+ && m_typeResolver->isPrimitive(valueType)) {
+ // Preferably store in QJSPrimitiveValue since we need the content pointer below.
+ property = property.storedIn(m_typeResolver->jsPrimitiveType());
+ } else {
+ property = property.storedIn(m_typeResolver->merge(specific.storedType(), valueType));
+ }
+ }
const QString object = registerVariable(baseReg);
m_body += u"{\n"_s;
@@ -1121,7 +1599,7 @@ void QQmlJSCodeGenerator::generate_SetLookup(int index, int baseReg)
QString preparation;
QString argType;
if (!m_typeResolver->registerContains(
- m_state.accumulatorIn(), m_typeResolver->containedType(property))) {
+ m_state.accumulatorIn(), property.containedType())) {
m_body += u"auto converted = "_s
+ conversion(m_state.accumulatorIn(), property, consumedAccumulatorVariableIn())
+ u";\n"_s;
@@ -1138,12 +1616,16 @@ void QQmlJSCodeGenerator::generate_SetLookup(int index, int baseReg)
argType = variableInType;
}
- switch (callBase.storedType()->accessSemantics()) {
+ switch (originalScope->accessSemantics()) {
case QQmlJSScope::AccessSemantics::Reference: {
+ const QString basePointer = resolveQObjectPointer(
+ originalScope, registerType(baseReg), object,
+ u"TypeError: Value is %1 and could not be converted to an object"_s);
+
const QString lookup = u"aotContext->setObjectLookup("_s + indexString
- + u", "_s + object + u", "_s + variableIn + u')';
+ + u", "_s + basePointer + u", "_s + variableIn + u')';
const QString initialization = u"aotContext->initSetObjectLookup("_s
- + indexString + u", "_s + object + u", "_s + argType + u')';
+ + indexString + u", "_s + basePointer + u", "_s + argType + u')';
generateLookup(lookup, initialization, preparation);
break;
}
@@ -1154,11 +1636,8 @@ void QQmlJSCodeGenerator::generate_SetLookup(int index, int baseReg)
break;
}
- if (!m_typeResolver->registerIsStoredIn(callBase, m_typeResolver->listPropertyType())) {
- if (m_typeResolver->canUseValueTypes())
- reject(u"resizing sequence types (because of missing write-back)"_s);
- else
- reject(u"resizing sequence type references"_s);
+ if (!originalScope->isListProperty()) {
+ reject(u"resizing sequence types (because of missing write-back)"_s);
break;
}
@@ -1175,24 +1654,21 @@ void QQmlJSCodeGenerator::generate_SetLookup(int index, int baseReg)
break;
}
case QQmlJSScope::AccessSemantics::Value: {
- const QString propertyName = m_jsUnitGenerator->lookupName(index);
- const QQmlJSRegisterContent specific = m_typeResolver->memberType(callBase, propertyName);
- Q_ASSERT(specific.isProperty());
- const QQmlJSRegisterContent property = specific.storedIn(
- m_typeResolver->genericType(specific.storedType()));
+ const QQmlJSRegisterContent base = registerType(baseReg);
+ const QString baseContentPointer = resolveValueTypeContentPointer(
+ originalScope, base, object,
+ u"TypeError: Value is %1 and could not be converted to an object"_s);
const QString lookup = u"aotContext->setValueLookup("_s + indexString
- + u", "_s + contentPointer(registerType(baseReg), object)
+ + u", "_s + baseContentPointer
+ u", "_s + variableIn + u')';
const QString initialization = u"aotContext->initSetValueLookup("_s
- + indexString + u", "_s + metaObject(property.scopeType())
- + u", "_s + contentType(registerType(baseReg), object) + u')';
+ + indexString + u", "_s + metaObject(originalScope)
+ + u", "_s + argType + u')';
generateLookup(lookup, initialization, preparation);
- if (m_typeResolver->canUseValueTypes())
- reject(u"SetLookup on value types (because of missing write-back)"_s);
- else
- reject(u"SetLookup on value type references"_s);
+ generateWriteBack(baseReg);
+
break;
}
case QQmlJSScope::AccessSemantics::None:
@@ -1234,7 +1710,6 @@ QString QQmlJSCodeGenerator::argumentsList(int argc, int argv, QString *outVar)
{
QString types;
QString args;
- QString conversions;
if (m_state.changedRegisterIndex() == InvalidRegister ||
m_typeResolver->registerContains(
@@ -1244,39 +1719,27 @@ QString QQmlJSCodeGenerator::argumentsList(int argc, int argv, QString *outVar)
} else {
*outVar = u"callResult"_s;
const QQmlJSScope::ConstPtr outType = m_state.accumulatorOut().storedType();
- m_body += outType->internalName();
- if (outType->accessSemantics() == QQmlJSScope::AccessSemantics::Reference)
- m_body += u" *"_s;
- else
- m_body += u' ';
- m_body += *outVar + u";\n";
+ m_body += outType->augmentedInternalName() + u' ' + *outVar;
+ if (!m_typeResolver->registerContains(m_state.accumulatorOut(), outType)) {
+ if (m_typeResolver->equals(outType, m_typeResolver->varType())
+ || m_typeResolver->equals(outType, m_typeResolver->jsPrimitiveType())) {
+ m_body += u'(' + metaType(m_state.accumulatorOut().containedType()) + u')';
+ }
+ }
+ m_body += u";\n";
- types = metaTypeFromType(m_state.accumulatorOut().storedType());
- args = u'&' + *outVar;
+ args = contentPointer(m_state.accumulatorOut(), *outVar);
+ types = contentType(m_state.accumulatorOut(), *outVar);
}
for (int i = 0; i < argc; ++i) {
const QQmlJSRegisterContent content = registerType(argv + i);
- if (m_typeResolver->registerIsStoredIn(content, m_typeResolver->jsPrimitiveType())) {
- QString argName = u"arg"_s + QString::number(i);
- conversions += u"QVariant "_s + argName + u" = "_s
- + conversion(content.storedType(), m_typeResolver->varType(),
- consumedRegisterVariable(argv + i)) + u";\n"_s;
- args += u", "_s + argName + u".data()"_s;
- types += u", "_s + argName + u".metaType()"_s;
- } else if (m_typeResolver->registerIsStoredIn(content, m_typeResolver->varType())
- && !m_typeResolver->registerContains(content, m_typeResolver->varType())) {
- const QString var = registerVariable(argv + i);
- args += u", "_s + var + u".data()"_s;
- types += u", "_s + var + u".metaType()"_s;
- } else {
- const QString var = registerVariable(argv + i);
- args += u", &"_s + var;
- types += u", "_s + metaTypeFromType(content.storedType());
- }
+ const QString var = registerVariable(argv + i);
+ args += u", "_s + contentPointer(content, var);
+ types += u", "_s + contentType(content, var);
}
- return conversions
- + u"void *args[] = { "_s + args + u" };\n"_s
+
+ return u"void *args[] = { "_s + args + u" };\n"_s
+ u"const QMetaType types[] = { "_s + types + u" };\n"_s;
}
@@ -1321,30 +1784,24 @@ bool QQmlJSCodeGenerator::inlineStringMethod(const QString &name, int base, int
return false;
const auto arg = [&](const QQmlJSScope::ConstPtr &type) {
- return conversion(registerType(argv).storedType(), type, consumedRegisterVariable(argv));
+ return convertStored(registerType(argv).storedType(), type, consumedRegisterVariable(argv));
};
const auto ret = [&](const QString &arg) {
- const QString expression = conversion(
+ const QString expression = convertStored(
registerType(base).storedType(), m_typeResolver->stringType(),
consumedRegisterVariable(base)) + u".arg("_s + arg + u')';
return conversion(
- m_typeResolver->stringType(), m_state.accumulatorOut().storedType(), expression);
+ m_typeResolver->stringType(), m_state.accumulatorOut(), expression);
};
const QQmlJSRegisterContent input = m_state.readRegister(argv);
m_body += m_state.accumulatorVariableOut + u" = "_s;
- if (m_typeResolver->registerContains(input, m_typeResolver->intType()))
- m_body += ret(arg(m_typeResolver->intType()));
- else if (m_typeResolver->registerContains(input, m_typeResolver->uintType()))
- m_body += ret(arg(m_typeResolver->uintType()));
+ if (m_typeResolver->isNumeric(input))
+ m_body += ret(arg(input.containedType()));
else if (m_typeResolver->registerContains(input, m_typeResolver->boolType()))
m_body += ret(arg(m_typeResolver->boolType()));
- else if (m_typeResolver->registerContains(input, m_typeResolver->realType()))
- m_body += ret(arg(m_typeResolver->realType()));
- else if (m_typeResolver->registerContains(input, m_typeResolver->floatType()))
- m_body += ret(arg(m_typeResolver->floatType()));
else
m_body += ret(arg(m_typeResolver->stringType()));
m_body += u";\n"_s;
@@ -1353,12 +1810,12 @@ bool QQmlJSCodeGenerator::inlineStringMethod(const QString &name, int base, int
bool QQmlJSCodeGenerator::inlineTranslateMethod(const QString &name, int argc, int argv)
{
- addInclude(u"qcoreapplication.h"_s);
+ addInclude(u"QtCore/qcoreapplication.h"_s);
const auto arg = [&](int i, const QQmlJSScope::ConstPtr &type) {
Q_ASSERT(i < argc);
- return conversion(registerType(argv + i).storedType(), type,
- consumedRegisterVariable(argv + i));
+ return convertStored(registerType(argv + i).storedType(), type,
+ consumedRegisterVariable(argv + i));
};
const auto stringArg = [&](int i) {
@@ -1368,12 +1825,12 @@ bool QQmlJSCodeGenerator::inlineTranslateMethod(const QString &name, int argc, i
};
const auto intArg = [&](int i) {
- return i < argc ? arg(i, m_typeResolver->intType()) : u"-1"_s;
+ return i < argc ? arg(i, m_typeResolver->int32Type()) : u"-1"_s;
};
const auto stringRet = [&](const QString &expression) {
return conversion(
- m_typeResolver->stringType(), m_state.accumulatorOut().storedType(), expression);
+ m_typeResolver->stringType(), m_state.accumulatorOut(), expression);
};
const auto capture = [&]() {
@@ -1427,13 +1884,45 @@ bool QQmlJSCodeGenerator::inlineTranslateMethod(const QString &name, int argc, i
return false;
}
+static QString maxExpression(int argc)
+{
+ Q_ASSERT_X(argc >= 2, Q_FUNC_INFO, "max() expects at least two arguments.");
+
+ QString expression =
+ u"[&]() { \nauto tmpMax = (qIsNull(arg2) && qIsNull(arg1) && std::copysign(1.0, arg2) == 1) ? arg2 : ((arg2 > arg1 || std::isnan(arg2)) ? arg2 : arg1);\n"_s;
+ for (int i = 2; i < argc; i++) {
+ expression +=
+ "\ttmpMax = (qIsNull(%1) && qIsNull(tmpMax) && std::copysign(1.0, %1) == 1) ? arg2 : ((%1 > tmpMax || std::isnan(%1)) ? %1 : tmpMax);\n"_L1
+ .arg("arg"_L1 + QString::number(i + 1));
+ }
+ expression += "return tmpMax;\n}()"_L1;
+
+ return expression;
+}
+
+static QString minExpression(int argc)
+{
+ Q_ASSERT_X(argc >= 2, Q_FUNC_INFO, "min() expects at least two arguments.");
+
+ QString expression =
+ u"[&]() { \nauto tmpMin = (qIsNull(arg2) && qIsNull(arg1) && std::copysign(1.0, arg2) == -1) ? arg2 : ((arg2 < arg1 || std::isnan(arg2)) ? arg2 : arg1);\n"_s;
+ for (int i = 2; i < argc; i++) {
+ expression +=
+ "tmpMin = (qIsNull(%1) && qIsNull(tmpMin) && std::copysign(1.0, %1) == -1) ? arg2 : ((%1 < tmpMin || std::isnan(%1)) ? %1 : tmpMin);\n"_L1
+ .arg("arg"_L1 + QString::number(i + 1));
+ }
+ expression += "return tmpMin;\n}()"_L1;
+
+ return expression;
+}
+
bool QQmlJSCodeGenerator::inlineMathMethod(const QString &name, int argc, int argv)
{
addInclude(u"cmath"_s);
addInclude(u"limits"_s);
- addInclude(u"qalgorithms.h"_s);
- addInclude(u"qrandom.h"_s);
- addInclude(u"qjsprimitivevalue.h"_s);
+ addInclude(u"QtCore/qalgorithms.h"_s);
+ addInclude(u"QtCore/qrandom.h"_s);
+ addInclude(u"QtQml/qjsprimitivevalue.h"_s);
// If the result is not stored, we don't need to generate any code. All the math methods are
// conceptually pure functions.
@@ -1442,7 +1931,7 @@ bool QQmlJSCodeGenerator::inlineMathMethod(const QString &name, int argc, int ar
m_body += u"{\n"_s;
for (int i = 0; i < argc; ++i) {
- m_body += u"const double arg%1 = "_s.arg(i + 1) + conversion(
+ m_body += u"const double arg%1 = "_s.arg(i + 1) + convertStored(
registerType(argv + i).storedType(),
m_typeResolver->realType(), consumedRegisterVariable(argv + i))
+ u";\n"_s;
@@ -1508,14 +1997,10 @@ bool QQmlJSCodeGenerator::inlineMathMethod(const QString &name, int argc, int ar
expression = u"arg1 < -1.0 ? %1 : std::log1p(arg1)"_s.arg(qNaN);
} else if (name == u"log2"_s && argc == 1) {
expression = u"arg1 < -0.0 ? %1 : std::log2(arg1)"_s.arg(qNaN);
- } else if (name == u"max"_s && argc == 2) {
- expression = u"(qIsNull(arg2) && qIsNull(arg1) && std::copysign(1.0, arg2) == 1) "
- "? arg2 "
- ": ((arg2 > arg1 || std::isnan(arg2)) ? arg2 : arg1)"_s;
- } else if (name == u"min"_s && argc == 2) {
- expression = u"(qIsNull(arg2) && qIsNull(arg1) && std::copysign(1.0, arg2) == -1) "
- "? arg2 "
- ": ((arg2 < arg1 || std::isnan(arg2)) ? arg2 : arg1)"_s;
+ } else if (name == u"max"_s && argc >= 2) {
+ expression = maxExpression(argc);
+ } else if (name == u"min"_s && argc >= 2) {
+ expression = minExpression(argc);
} else if (name == u"pow"_s) {
expression = u"QQmlPrivate::jsExponentiate(arg1, arg2)"_s;
} else if (name == u"random"_s && argc == 0) {
@@ -1548,8 +2033,7 @@ bool QQmlJSCodeGenerator::inlineMathMethod(const QString &name, int argc, int ar
return false;
}
- m_body += conversion(
- m_typeResolver->realType(), m_state.accumulatorOut().storedType(), expression);
+ m_body += conversion(m_typeResolver->realType(), m_state.accumulatorOut(), expression);
m_body += u";\n"_s;
m_body += u"}\n"_s;
@@ -1575,23 +2059,21 @@ bool QQmlJSCodeGenerator::inlineConsoleMethod(const QString &name, int argc, int
if (type.isEmpty())
return false;
- addInclude(u"qloggingcategory.h"_s);
+ addInclude(u"QtCore/qloggingcategory.h"_s);
m_body += u"{\n";
m_body += u" bool firstArgIsCategory = false;\n";
const QQmlJSRegisterContent firstArg = argc > 0 ? registerType(argv) : QQmlJSRegisterContent();
- // We could check for internalName == "QQmlLoggingCategory" here, but we don't want to
- // because QQmlLoggingCategory is not a builtin. Tying the specific internal name and
- // intheritance hierarchy in here would be fragile.
- // TODO: We could drop the check for firstArg in some cases if we made some base class
- // of QQmlLoggingCategory a builtin.
+ // We could check whether the first argument is a QQmlLoggingCategoryBase here, and we should
+ // because QQmlLoggingCategoryBase is now a builtin.
+ // TODO: The run time check for firstArg is obsolete.
const bool firstArgIsReference = argc > 0
- && m_typeResolver->containedType(firstArg)->isReferenceType();
+ && firstArg.containedType()->isReferenceType();
if (firstArgIsReference) {
m_body += u" QObject *firstArg = ";
- m_body += conversion(
+ m_body += convertStored(
firstArg.storedType(),
m_typeResolver->genericType(firstArg.storedType()),
registerVariable(argv));
@@ -1604,18 +2086,35 @@ bool QQmlJSCodeGenerator::inlineConsoleMethod(const QString &name, int argc, int
m_body += u" if (category && category->isEnabled(" + type + u")) {\n";
m_body += u" const QString message = ";
+
+ const auto stringConversion = [&](int i) -> QString {
+ const QQmlJSScope::ConstPtr stored = m_state.readRegister(argv + i).storedType();
+ if (m_typeResolver->equals(stored, m_typeResolver->stringType())) {
+ return convertStored(
+ registerType(argv + i).storedType(),
+ m_typeResolver->stringType(), consumedRegisterVariable(argv + i));
+ } else if (stored->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence) {
+ addInclude(u"QtQml/qjslist.h"_s);
+ return u"u'[' + QJSList(&"_s + registerVariable(argv + i)
+ + u", aotContext->engine).toString() + u']'"_s;
+ } else {
+ reject(u"converting arguments for console method to string"_s);
+ return QString();
+ }
+ };
+
if (argc > 0) {
- const QString firstArgStringConversion = conversion(
+ if (firstArgIsReference) {
+ const QString firstArgStringConversion = convertStored(
registerType(argv).storedType(),
m_typeResolver->stringType(), registerVariable(argv));
- if (firstArgIsReference) {
m_body += u"(firstArgIsCategory ? QString() : (" + firstArgStringConversion;
if (argc > 1)
m_body += u".append(QLatin1Char(' ')))).append(";
else
m_body += u"))";
} else {
- m_body += firstArgStringConversion;
+ m_body += stringConversion(0);
if (argc > 1)
m_body += u".append(QLatin1Char(' ')).append(";
}
@@ -1623,21 +2122,98 @@ bool QQmlJSCodeGenerator::inlineConsoleMethod(const QString &name, int argc, int
for (int i = 1; i < argc; ++i) {
if (i > 1)
m_body += u".append(QLatin1Char(' ')).append("_s;
- m_body += conversion(
- registerType(argv + i).storedType(),
- m_typeResolver->stringType(), consumedRegisterVariable(argv + i)) + u')';
+ m_body += stringConversion(i) + u')';
}
} else {
m_body += u"QString()";
}
- m_body += u";\n";
-
+ m_body += u";\n ";
+ generateSetInstructionPointer();
m_body += u" aotContext->writeToConsole(" + type + u", message, category);\n";
m_body += u" }\n";
m_body += u"}\n";
return true;
}
+bool QQmlJSCodeGenerator::inlineArrayMethod(const QString &name, int base, int argc, int argv)
+{
+ const auto intType = m_typeResolver->int32Type();
+ const auto valueType = registerType(base).storedType()->valueType();
+ const auto boolType = m_typeResolver->boolType();
+ const auto stringType = m_typeResolver->stringType();
+ const auto baseType = registerType(base);
+
+ const QString baseVar = registerVariable(base);
+ const QString qjsListMethod = u"QJSList(&"_s + baseVar + u", aotContext->engine)."
+ + name + u"(";
+
+ addInclude(u"QtQml/qjslist.h"_s);
+
+ if (name == u"includes" && argc > 0 && argc < 3) {
+ QString call = qjsListMethod
+ + convertStored(registerType(argv).storedType(), valueType,
+ consumedRegisterVariable(argv));
+ if (argc == 2) {
+ call += u", " + convertStored(registerType(argv + 1).storedType(), intType,
+ consumedRegisterVariable(argv + 1));
+ }
+ call += u")";
+
+ m_body += m_state.accumulatorVariableOut + u" = "_s
+ + conversion(boolType, m_state.accumulatorOut(), call) + u";\n"_s;
+ return true;
+ }
+
+ if (name == u"toString" || (name == u"join" && argc < 2)) {
+ QString call = qjsListMethod;
+ if (argc == 1) {
+ call += convertStored(registerType(argv).storedType(), stringType,
+ consumedRegisterVariable(argv));
+ }
+ call += u")";
+
+ m_body += m_state.accumulatorVariableOut + u" = "_s
+ + conversion(stringType, m_state.accumulatorOut(), call) + u";\n"_s;
+ return true;
+ }
+
+ if (name == u"slice" && argc < 3) {
+ QString call = qjsListMethod;
+ for (int i = 0; i < argc; ++i) {
+ if (i > 0)
+ call += u", ";
+ call += convertStored(registerType(argv + i).storedType(), intType,
+ consumedRegisterVariable(argv + i));
+ }
+ call += u")";
+
+ const auto outType = baseType.storedType()->isListProperty()
+ ? global(m_typeResolver->qObjectListType())
+ : baseType;
+
+ m_body += m_state.accumulatorVariableOut + u" = "_s
+ + conversion(outType, m_state.accumulatorOut(), call) + u";\n"_s;
+ return true;
+ }
+
+ if ((name == u"indexOf" || name == u"lastIndexOf") && argc > 0 && argc < 3) {
+ QString call = qjsListMethod
+ + convertStored(registerType(argv).storedType(), valueType,
+ consumedRegisterVariable(argv));
+ if (argc == 2) {
+ call += u", " + convertStored(registerType(argv + 1).storedType(), intType,
+ consumedRegisterVariable(argv + 1));
+ }
+ call += u")";
+
+ m_body += m_state.accumulatorVariableOut + u" = "_s
+ + conversion(intType, m_state.accumulatorOut(), call) + u";\n"_s;
+ return true;
+ }
+
+ return false;
+}
+
void QQmlJSCodeGenerator::generate_CallPropertyLookup(int index, int base, int argc, int argv)
{
INJECT_TRACE_INFO(generate_CallPropertyLookup);
@@ -1645,38 +2221,36 @@ void QQmlJSCodeGenerator::generate_CallPropertyLookup(int index, int base, int a
if (m_state.accumulatorOut().variant() == QQmlJSRegisterContent::JavaScriptReturnValue)
reject(u"call to untyped JavaScript function"_s);
+ const QQmlJSScope::ConstPtr scope = m_state.accumulatorOut().scopeType();
+
AccumulatorConverter registers(this);
const QQmlJSRegisterContent baseType = registerType(base);
- if (baseType.storedType()->accessSemantics() != QQmlJSScope::AccessSemantics::Reference) {
- const QString name = m_jsUnitGenerator->stringForIndex(
- m_jsUnitGenerator->lookupNameIndex(index));
- if (m_typeResolver->equals(m_typeResolver->originalContainedType(baseType), mathObject())) {
- if (inlineMathMethod(name, argc, argv))
- return;
- }
-
- if (m_typeResolver->equals(m_typeResolver->originalContainedType(baseType), consoleObject())) {
- if (inlineConsoleMethod(name, argc, argv))
- return;
- }
+ const QString name = m_jsUnitGenerator->lookupName(index);
- if (m_typeResolver->equals(m_typeResolver->originalContainedType(baseType),
- m_typeResolver->stringType())) {
- if (inlineStringMethod(name, base, argc, argv))
- return;
- }
+ if (m_typeResolver->equals(scope, m_typeResolver->mathObject())) {
+ if (inlineMathMethod(name, argc, argv))
+ return;
+ } else if (m_typeResolver->equals(scope, m_typeResolver->consoleObject())) {
+ if (inlineConsoleMethod(name, argc, argv))
+ return;
+ } else if (m_typeResolver->equals(scope, m_typeResolver->stringType())) {
+ if (inlineStringMethod(name, base, argc, argv))
+ return;
+ } else if (baseType.storedType()->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence) {
+ if (inlineArrayMethod(name, base, argc, argv))
+ return;
+ }
- if (m_typeResolver->canUseValueTypes()) {
- // This is possible, once we establish the right kind of lookup for it
- reject(u"call to property '%1' of %2"_s.arg(name, baseType.descriptiveName()));
- } else {
- // This is not possible.
- reject(u"call to property '%1' of value type reference %2"_s
- .arg(name, baseType.descriptiveName()));
- }
+ if (!scope->isReferenceType()) {
+ // This is possible, once we establish the right kind of lookup for it
+ reject(u"call to property '%1' of %2"_s.arg(name, baseType.descriptiveName()));
}
+ const QString inputPointer = resolveQObjectPointer(
+ scope, baseType, registerVariable(base),
+ u"Cannot call method '%1' of %2"_s.arg(name));
+
const QString indexString = QString::number(index);
m_body += u"{\n"_s;
@@ -1684,7 +2258,7 @@ void QQmlJSCodeGenerator::generate_CallPropertyLookup(int index, int base, int a
QString outVar;
m_body += argumentsList(argc, argv, &outVar);
const QString lookup = u"aotContext->callObjectPropertyLookup("_s + indexString
- + u", "_s + registerVariable(base)
+ + u", "_s + inputPointer
+ u", args, types, "_s + QString::number(argc) + u')';
const QString initialization = u"aotContext->initCallObjectPropertyLookup("_s
+ indexString + u')';
@@ -1769,9 +2343,83 @@ void QQmlJSCodeGenerator::generate_TailCall(int func, int thisObject, int argc,
void QQmlJSCodeGenerator::generate_Construct(int func, int argc, int argv)
{
+ INJECT_TRACE_INFO(generate_Construct);
Q_UNUSED(func);
- Q_UNUSED(argc);
- Q_UNUSED(argv);
+
+ const auto originalResult = original(m_state.accumulatorOut());
+
+ if (m_typeResolver->registerContains(originalResult, m_typeResolver->dateTimeType())) {
+ m_body += m_state.accumulatorVariableOut + u" = ";
+ if (argc == 0) {
+ m_body += conversion(
+ m_typeResolver->dateTimeType(), m_state.accumulatorOut(),
+ u"QDateTime::currentDateTime()"_s) + u";\n";
+ return;
+ }
+
+ if (argc == 1
+ && m_typeResolver->registerContains(
+ m_state.readRegister(argv), m_typeResolver->dateTimeType())) {
+ m_body += conversion(
+ registerType(argv), m_state.readRegister(argv), registerVariable(argv))
+ + u";\n";
+ return;
+ }
+
+ QString ctorArgs;
+ constexpr int maxArgc = 7; // year, month, day, hours, minutes, seconds, milliseconds
+ for (int i = 0; i < std::min(argc, maxArgc); ++i) {
+ if (i > 0)
+ ctorArgs += u", ";
+ ctorArgs += conversion(
+ registerType(argv + i), m_state.readRegister(argv + i),
+ registerVariable(argv + i));
+ }
+ m_body += conversion(
+ m_typeResolver->dateTimeType(), m_state.accumulatorOut(),
+ u"aotContext->constructDateTime("_s + ctorArgs + u')') + u";\n";
+ return;
+ }
+
+ if (m_typeResolver->registerContains(originalResult, m_typeResolver->variantListType())) {
+ rejectIfBadArray();
+
+ if (argc == 1
+ && m_typeResolver->registerContains(
+ m_state.readRegister(argv), m_typeResolver->realType())) {
+ addInclude(u"QtQml/qjslist.h"_s);
+
+ const QString error = u" aotContext->engine->throwError(QJSValue::RangeError, "_s
+ + u"QLatin1String(\"Invalid array length\"));\n"_s;
+
+ const QString indexName = registerVariable(argv);
+ const auto indexType = registerType(argv).containedType();
+ if (!m_typeResolver->isNativeArrayIndex(indexType)) {
+ m_body += u"if (!QJSNumberCoercion::isArrayIndex("_s + indexName + u")) {\n"_s
+ + error;
+ generateReturnError();
+ m_body += u"}\n"_s;
+ } else if (!m_typeResolver->isUnsignedInteger(indexType)) {
+ m_body += u"if ("_s + indexName + u" < 0) {\n"_s
+ + error;
+ generateReturnError();
+ m_body += u"}\n"_s;
+ }
+
+ m_body += m_state.accumulatorVariableOut + u" = "_s
+ + m_state.accumulatorOut().storedType()->internalName() + u"();\n"_s;
+ m_body += u"QJSList(&"_s + m_state.accumulatorVariableOut
+ + u", aotContext->engine).resize("_s
+ + convertStored(
+ registerType(argv).storedType(), m_typeResolver->sizeType(),
+ consumedRegisterVariable(argv))
+ + u");\n"_s;
+ } else if (!m_error->isValid()) {
+ generateArrayInitializer(argc, argv);
+ }
+ return;
+ }
+
reject(u"Construct"_s);
}
@@ -1804,7 +2452,9 @@ void QQmlJSCodeGenerator::generate_UnwindToLabel(int level, int offset)
void QQmlJSCodeGenerator::generate_DeadTemporalZoneCheck(int name)
{
Q_UNUSED(name)
- BYTECODE_UNIMPLEMENTED();
+ INJECT_TRACE_INFO(generate_DeadTemporalZoneCheck);
+ // Nothing to do here. If we have statically asserted the dtz check in the type propagator
+ // the value cannot be empty. Otherwise we can't get here.
}
void QQmlJSCodeGenerator::generate_ThrowException()
@@ -1813,10 +2463,9 @@ void QQmlJSCodeGenerator::generate_ThrowException()
generateSetInstructionPointer();
m_body += u"aotContext->engine->throwError("_s
- + conversion(m_state.accumulatorIn(), m_typeResolver->globalType(
- m_typeResolver->jsValueType()),
+ + conversion(m_state.accumulatorIn(), global(m_typeResolver->jsValueType()),
m_state.accumulatorVariableIn) + u");\n"_s;
- m_body += u"return "_s + errorReturnValue() + u";\n"_s;
+ generateReturnError();
m_skipUntilNextLabel = true;
resetState();
}
@@ -1876,33 +2525,100 @@ void QQmlJSCodeGenerator::generate_PopContext()
{
INJECT_TRACE_INFO(generate_PopContext);
- // Add a semicolon before the closing brace, in case there was a bare label before it.
- m_body += u";}\n"_s;
+ // Add an empty block before the closing brace, in case there was a bare label before it.
+ m_body += u"{}\n}\n"_s;
}
void QQmlJSCodeGenerator::generate_GetIterator(int iterator)
{
- Q_UNUSED(iterator)
- BYTECODE_UNIMPLEMENTED();
+ INJECT_TRACE_INFO(generate_GetIterator);
+
+ addInclude(u"QtQml/qjslist.h"_s);
+ const QQmlJSRegisterContent listType = m_state.accumulatorIn();
+ if (!listType.isList())
+ reject(u"iterator on non-list type"_s);
+
+ const QQmlJSRegisterContent iteratorType = m_state.accumulatorOut();
+ if (!iteratorType.isProperty()) {
+ reject(u"using non-iterator as iterator"_s);
+ return;
+ }
+
+ const QString identifier = QString::number(iteratorType.baseLookupIndex());
+ const QString iteratorName = m_state.accumulatorVariableOut + u"Iterator" + identifier;
+ const QString listName = m_state.accumulatorVariableOut + u"List" + identifier;
+
+ m_body += u"QJSListFor"_s
+ + (iterator == int(QQmlJS::AST::ForEachType::In) ? u"In"_s : u"Of"_s)
+ + u"Iterator "_s + iteratorName + u";\n";
+ m_body += m_state.accumulatorVariableOut + u" = &" + iteratorName + u";\n";
+
+ m_body += m_state.accumulatorVariableOut + u"->init(";
+ if (iterator == int(QQmlJS::AST::ForEachType::In)) {
+ if (!m_typeResolver->equals(iteratorType.storedType(), m_typeResolver->forInIteratorPtr()))
+ reject(u"using non-iterator as iterator"_s);
+ m_body += u"QJSList(&" + m_state.accumulatorVariableIn + u", aotContext->engine)";
+ }
+ m_body += u");\n";
+
+ if (iterator == int(QQmlJS::AST::ForEachType::Of)) {
+ if (!m_typeResolver->equals(iteratorType.storedType(), m_typeResolver->forOfIteratorPtr()))
+ reject(u"using non-iterator as iterator"_s);
+ m_body += u"const auto &" // Rely on life time extension for const refs
+ + listName + u" = " + consumedAccumulatorVariableIn();
+ }
}
-void QQmlJSCodeGenerator::generate_IteratorNext(int value, int done)
+void QQmlJSCodeGenerator::generate_IteratorNext(int value, int offset)
{
- Q_UNUSED(value)
- Q_UNUSED(done)
- BYTECODE_UNIMPLEMENTED();
+ INJECT_TRACE_INFO(generate_IteratorNext);
+
+ Q_ASSERT(value == m_state.changedRegisterIndex());
+ const QQmlJSRegisterContent iteratorContent = m_state.accumulatorIn();
+ if (!iteratorContent.isProperty()) {
+ reject(u"using non-iterator as iterator"_s);
+ return;
+ }
+
+ const QQmlJSScope::ConstPtr iteratorType = iteratorContent.storedType();
+ const QString iteratorTypeName = iteratorType->internalName();
+ const QString listName = m_state.accumulatorVariableIn
+ + u"List" + QString::number(iteratorContent.baseLookupIndex());
+ QString qjsList;
+ if (m_typeResolver->equals(iteratorType, m_typeResolver->forOfIteratorPtr()))
+ qjsList = u"QJSList(&" + listName + u", aotContext->engine)";
+ else if (!m_typeResolver->equals(iteratorType, m_typeResolver->forInIteratorPtr()))
+ reject(u"using non-iterator as iterator"_s);
+
+ m_body += u"if (" + m_state.accumulatorVariableIn + u"->hasNext(" + qjsList + u")) {\n ";
+
+ // We know that this works because we can do ->next() below.
+ QQmlJSRegisterContent iteratorValue = m_typeResolver->valueType(iteratorContent);
+ iteratorValue = iteratorValue.storedIn(iteratorValue.containedType());
+
+ m_body += changedRegisterVariable() + u" = "
+ + conversion(
+ iteratorValue, m_state.changedRegister(),
+ m_state.accumulatorVariableIn + u"->next(" + qjsList + u')')
+ + u";\n";
+ m_body += u"} else {\n ";
+ m_body += changedRegisterVariable() + u" = "
+ + conversion(m_typeResolver->voidType(), m_state.changedRegister(), QString());
+ m_body += u";\n ";
+ generateJumpCodeWithTypeConversions(offset);
+ m_body += u"\n}"_s;
}
-void QQmlJSCodeGenerator::generate_IteratorNextForYieldStar(int iterator, int object)
+void QQmlJSCodeGenerator::generate_IteratorNextForYieldStar(int iterator, int object, int offset)
{
Q_UNUSED(iterator)
Q_UNUSED(object)
+ Q_UNUSED(offset)
BYTECODE_UNIMPLEMENTED();
}
-void QQmlJSCodeGenerator::generate_IteratorClose(int done)
+void QQmlJSCodeGenerator::generate_IteratorClose()
{
- Q_UNUSED(done)
BYTECODE_UNIMPLEMENTED();
}
@@ -1946,55 +2662,136 @@ void QQmlJSCodeGenerator::generate_DefineArray(int argc, int args)
{
INJECT_TRACE_INFO(generate_DefineArray);
- const QQmlJSScope::ConstPtr stored = m_state.accumulatorOut().storedType();
+ rejectIfBadArray();
+ if (!m_error->isValid())
+ generateArrayInitializer(argc, args);
+}
- if (argc == 0) {
- m_body += m_state.accumulatorVariableOut + u" = "_s;
- m_body += conversion(m_typeResolver->emptyListType(), stored, QString());
- m_body += u";\n"_s;
- generateOutputVariantConversion(m_typeResolver->emptyListType());
+void QQmlJSCodeGenerator::generate_DefineObjectLiteral(int internalClassId, int argc, int args)
+{
+ INJECT_TRACE_INFO(generate_DefineObjectLiteral);
+
+ const QQmlJSScope::ConstPtr stored = m_state.accumulatorOut().storedType();
+ if (stored->accessSemantics() != QQmlJSScope::AccessSemantics::Value) {
+ reject(u"storing an object literal in a non-value type"_s);
return;
}
- if (stored->accessSemantics() != QQmlJSScope::AccessSemantics::Sequence) {
- // This rejects any attempt to store the list into a QVariant.
- // Therefore, we don't have to adjust the contained type below.
- reject(u"storing an array in a non-sequence type"_s);
+ const QQmlJSScope::ConstPtr contained = m_state.accumulatorOut().containedType();
+
+ const int classSize = m_jsUnitGenerator->jsClassSize(internalClassId);
+ Q_ASSERT(argc >= classSize);
+
+ if (m_typeResolver->equals(contained, m_typeResolver->varType())
+ || m_typeResolver->equals(contained, m_typeResolver->variantMapType())) {
+
+ m_body += m_state.accumulatorVariableOut + u" = QVariantMap {\n";
+ const QQmlJSScope::ConstPtr propType = m_typeResolver->varType();
+ for (int i = 0; i < classSize; ++i) {
+ m_body += u"{ "_s
+ + QQmlJSUtils::toLiteral(m_jsUnitGenerator->jsClassMember(internalClassId, i))
+ + u", "_s;
+ const int currentArg = args + i;
+ const QQmlJSScope::ConstPtr argType = registerType(currentArg).storedType();
+ const QString consumedArg = consumedRegisterVariable(currentArg);
+ m_body += convertStored(argType, propType, consumedArg) + u" },\n";
+ }
+
+ for (int i = classSize; i < argc; i += 3) {
+ const int nameArg = args + i + 1;
+ m_body += u"{ "_s
+ + conversion(
+ registerType(nameArg),
+ global(m_typeResolver->stringType()),
+ consumedRegisterVariable(nameArg))
+ + u", "_s;
+
+ const int valueArg = args + i + 2;
+ m_body += convertStored(
+ registerType(valueArg).storedType(),
+ propType,
+ consumedRegisterVariable(valueArg))
+ + u" },\n";
+ }
+
+ m_body += u"};\n";
return;
}
- const QQmlJSScope::ConstPtr value = stored->valueType();
- Q_ASSERT(value);
+ m_body += m_state.accumulatorVariableOut + u" = "_s + stored->augmentedInternalName();
+ const bool isVariantOrPrimitive = m_typeResolver->equals(stored, m_typeResolver->varType())
+ || m_typeResolver->equals(stored, m_typeResolver->jsPrimitiveType());
- QStringList initializer;
- for (int i = 0; i < argc; ++i) {
- initializer += conversion(registerType(args + i).storedType(), value,
- consumedRegisterVariable(args + i));
+ if (m_typeResolver->registerContains(m_state.accumulatorOut(), stored)) {
+ m_body += u"()";
+ } else if (isVariantOrPrimitive) {
+ m_body += u'(' + metaType(m_state.accumulatorOut().containedType()) + u')';
+ } else {
+ reject(u"storing an object literal in an unsupported container %1"_s
+ .arg(stored->internalName()));
}
+ m_body += u";\n";
- if (stored->isListProperty()) {
- reject(u"creating a QQmlListProperty not backed by a property"_s);
+ if (argc == 0)
+ return;
- // We can, technically, generate code for this. But it's dangerous:
- //
- // const QString storage = m_state.accumulatorVariableOut + u"_storage"_s;
- // m_body += stored->internalName() + u"::ListType " + storage
- // + u" = {"_s + initializer.join(u", "_s) + u"};\n"_s;
- // m_body += m_state.accumulatorVariableOut
- // + u" = " + stored->internalName() + u"(nullptr, &"_s + storage + u");\n"_s;
- } else {
- m_body += m_state.accumulatorVariableOut + u" = "_s + stored->internalName() + u'{';
- m_body += initializer.join(u", "_s);
- m_body += u"};\n";
+ bool isExtension = false;
+ if (!m_typeResolver->canPopulate(contained, m_typeResolver->variantMapType(), &isExtension)) {
+ reject(u"storing an object literal in a non-structured value type"_s);
}
-}
-void QQmlJSCodeGenerator::generate_DefineObjectLiteral(int internalClassId, int argc, int args)
-{
- Q_UNUSED(internalClassId)
- Q_UNUSED(argc)
- Q_UNUSED(args)
- reject(u"DefineObjectLiteral"_s);
+ const QQmlJSScope::ConstPtr accessor = isExtension
+ ? contained->extensionType().scope
+ : contained;
+
+ m_body += u"{\n";
+ m_body += u" const QMetaObject *meta = ";
+ if (!isExtension && isVariantOrPrimitive)
+ m_body += m_state.accumulatorVariableOut + u".metaType().metaObject()";
+ else
+ m_body += metaObject(accessor);
+ m_body += u";\n";
+
+ for (int i = 0; i < classSize; ++i) {
+ m_body += u" {\n";
+ const QString propName = m_jsUnitGenerator->jsClassMember(internalClassId, i);
+ const int currentArg = args + i;
+ const QQmlJSRegisterContent propType = m_state.readRegister(currentArg);
+ const QQmlJSRegisterContent argType = registerType(currentArg);
+ const QQmlJSMetaProperty property = contained->property(propName);
+ const QString consumedArg = consumedRegisterVariable(currentArg);
+ QString argument = conversion(argType, propType, consumedArg);
+
+ if (argument == consumedArg) {
+ argument = registerVariable(currentArg);
+ } else {
+ m_body += u" auto arg = "_s + argument + u";\n";
+ argument = u"arg"_s;
+ }
+
+ int index = property.index();
+ if (index == -1)
+ continue;
+
+ m_body += u" void *argv[] = { %1, nullptr };\n"_s
+ .arg(contentPointer(propType, argument));
+ m_body += u" meta->d.static_metacall(reinterpret_cast<QObject *>(";
+ m_body += contentPointer(m_state.accumulatorOut(), m_state.accumulatorVariableOut);
+ m_body += u"), QMetaObject::WriteProperty, ";
+ m_body += QString::number(index) + u", argv);\n";
+ m_body += u" }\n";
+ }
+
+ // This is not implemented because we cannot statically determine the type of the value and we
+ // don't want to rely on QVariant::convert() since that may give different results than
+ // the JavaScript coercion. We might still make it work by querying the QMetaProperty
+ // for its type at run time and runtime coercing to that, but we don't know whether that
+ // still pays off.
+ if (argc > classSize)
+ reject(u"non-literal keys of object literals"_s);
+
+ m_body += u"}\n";
+
}
void QQmlJSCodeGenerator::generate_CreateClass(int classIndex, int heritage, int computedNames)
@@ -2023,7 +2820,12 @@ void QQmlJSCodeGenerator::generate_CreateRestParameter(int argIndex)
void QQmlJSCodeGenerator::generate_ConvertThisToObject()
{
- BYTECODE_UNIMPLEMENTED();
+ INJECT_TRACE_INFO(generate_ConvertThisToObject);
+
+ m_body += changedRegisterVariable() + u" = "_s
+ + conversion(m_typeResolver->qObjectType(), m_state.changedRegister(),
+ u"aotContext->thisObject()"_s)
+ + u";\n"_s;
}
void QQmlJSCodeGenerator::generate_LoadSuperConstructor()
@@ -2041,7 +2843,6 @@ void QQmlJSCodeGenerator::generate_Jump(int offset)
INJECT_TRACE_INFO(generate_Jump);
generateJumpCodeWithTypeConversions(offset);
- m_body += u";\n"_s;
m_skipUntilNextLabel = true;
resetState();
}
@@ -2051,11 +2852,10 @@ void QQmlJSCodeGenerator::generate_JumpTrue(int offset)
INJECT_TRACE_INFO(generate_JumpTrue);
m_body += u"if ("_s;
- m_body += conversion(m_state.accumulatorIn().storedType(), m_typeResolver->boolType(),
- m_state.accumulatorVariableIn);
+ m_body += convertStored(m_state.accumulatorIn().storedType(), m_typeResolver->boolType(),
+ m_state.accumulatorVariableIn);
m_body += u") "_s;
generateJumpCodeWithTypeConversions(offset);
- m_body += u";\n"_s;
}
void QQmlJSCodeGenerator::generate_JumpFalse(int offset)
@@ -2063,11 +2863,10 @@ void QQmlJSCodeGenerator::generate_JumpFalse(int offset)
INJECT_TRACE_INFO(generate_JumpFalse);
m_body += u"if (!"_s;
- m_body += conversion(m_state.accumulatorIn().storedType(), m_typeResolver->boolType(),
- m_state.accumulatorVariableIn);
+ m_body += convertStored(m_state.accumulatorIn().storedType(), m_typeResolver->boolType(),
+ m_state.accumulatorVariableIn);
m_body += u") "_s;
generateJumpCodeWithTypeConversions(offset);
- m_body += u";\n"_s;
}
void QQmlJSCodeGenerator::generate_JumpNoException(int offset)
@@ -2076,7 +2875,6 @@ void QQmlJSCodeGenerator::generate_JumpNoException(int offset)
m_body += u"if (!context->engine->hasException()) "_s;
generateJumpCodeWithTypeConversions(offset);
- m_body += u";\n"_s;
}
void QQmlJSCodeGenerator::generate_JumpNotUndefined(int offset)
@@ -2094,63 +2892,14 @@ void QQmlJSCodeGenerator::generate_CheckException()
void QQmlJSCodeGenerator::generate_CmpEqNull()
{
- INJECT_TRACE_INFO(generate_CmlEqNull);
-
- m_body += m_state.accumulatorVariableOut + u" = "_s;
- m_body += conversion(
- m_typeResolver->boolType(), m_state.accumulatorOut().storedType(),
- u"QJSPrimitiveValue(QJSPrimitiveNull()).equals("_s
- + conversion(
- m_state.accumulatorIn().storedType(), m_typeResolver->jsPrimitiveType(),
- consumedAccumulatorVariableIn()) + u')');
- m_body += u";\n"_s;
- generateOutputVariantConversion(m_typeResolver->boolType());
+ INJECT_TRACE_INFO(generate_CmpEqNull);
+ generateEqualityOperation(global(m_typeResolver->nullType()), QString(), u"equals"_s, false);
}
void QQmlJSCodeGenerator::generate_CmpNeNull()
{
INJECT_TRACE_INFO(generate_CmlNeNull);
-
- m_body += m_state.accumulatorVariableOut + u" = "_s;
- m_body += conversion(
- m_typeResolver->boolType(), m_state.accumulatorOut().storedType(),
- u"!QJSPrimitiveValue(QJSPrimitiveNull()).equals("_s
- + conversion(
- m_state.accumulatorIn().storedType(), m_typeResolver->jsPrimitiveType(),
- consumedAccumulatorVariableIn()) + u')');
- m_body += u";\n"_s;
- generateOutputVariantConversion(m_typeResolver->boolType());
-}
-
-QString QQmlJSCodeGenerator::eqIntExpression(int lhsConst)
-{
- if (m_typeResolver->registerIsStoredIn(m_state.accumulatorIn(), m_typeResolver->intType())
- || m_typeResolver->registerIsStoredIn(
- m_state.accumulatorIn(), m_typeResolver->uintType())) {
- return QString::number(lhsConst) + u" == "_s + m_state.accumulatorVariableIn;
- }
-
- if (m_typeResolver->registerIsStoredIn(m_state.accumulatorIn(), m_typeResolver->boolType())) {
- return QString::number(lhsConst) + u" == "_s
- + conversion(m_state.accumulatorIn().storedType(), m_typeResolver->intType(),
- consumedAccumulatorVariableIn());
- }
-
- if (m_typeResolver->isNumeric(m_state.accumulatorIn())) {
- return conversion(m_typeResolver->intType(), m_typeResolver->realType(),
- QString::number(lhsConst)) + u" == "_s
- + conversion(m_state.accumulatorIn().storedType(), m_typeResolver->realType(),
- consumedAccumulatorVariableIn());
- }
-
- QString result;
- result += conversion(m_typeResolver->intType(), m_typeResolver->jsPrimitiveType(),
- QString::number(lhsConst));
- result += u".equals("_s;
- result += conversion(m_state.accumulatorIn().storedType(), m_typeResolver->jsPrimitiveType(),
- consumedAccumulatorVariableIn());
- result += u')';
- return result;
+ generateEqualityOperation(global(m_typeResolver->nullType()), QString(), u"equals"_s, true);
}
QString QQmlJSCodeGenerator::getLookupPreparation(
@@ -2159,7 +2908,7 @@ QString QQmlJSCodeGenerator::getLookupPreparation(
if (m_typeResolver->registerContains(content, content.storedType()))
return QString();
- if (m_typeResolver->registerIsStoredIn(content, m_typeResolver->varType())) {
+ if (registerIsStoredIn(content, m_typeResolver->varType())) {
return var + u" = QVariant(aotContext->lookupResultMetaType("_s
+ QString::number(lookup) + u"))"_s;
}
@@ -2173,47 +2922,49 @@ QString QQmlJSCodeGenerator::contentPointer(const QQmlJSRegisterContent &content
if (m_typeResolver->registerContains(content, stored))
return u'&' + var;
- if (m_typeResolver->registerIsStoredIn(content, m_typeResolver->varType()))
+ if (registerIsStoredIn(content, m_typeResolver->varType())
+ || registerIsStoredIn(content, m_typeResolver->jsPrimitiveType())) {
return var + u".data()"_s;
+ }
if (stored->accessSemantics() == QQmlJSScope::AccessSemantics::Reference)
return u'&' + var;
- if (m_typeResolver->registerIsStoredIn(content, m_typeResolver->intType())
- && m_typeResolver->containedType(content)->scopeType() == QQmlJSScope::EnumScope) {
+ if (m_typeResolver->isNumeric(content.storedType())
+ && content.containedType()->scopeType() == QQmlSA::ScopeType::EnumScope) {
return u'&' + var;
}
- if (stored->isListProperty() && m_typeResolver->containedType(content)->isListProperty())
+ if (stored->isListProperty() && content.containedType()->isListProperty())
return u'&' + var;
- reject(u"content pointer of non-QVariant wrapper type "_s + content.descriptiveName());
+ reject(u"content pointer of unsupported wrapper type "_s + content.descriptiveName());
return QString();
}
QString QQmlJSCodeGenerator::contentType(const QQmlJSRegisterContent &content, const QString &var)
{
const QQmlJSScope::ConstPtr stored = content.storedType();
- const QQmlJSScope::ConstPtr contained = QQmlJSScope::nonCompositeBaseType(
- m_typeResolver->containedType(content));
+ const QQmlJSScope::ConstPtr contained = content.containedType();
if (m_typeResolver->equals(contained, stored))
return metaTypeFromType(stored);
- if (m_typeResolver->equals(stored, m_typeResolver->varType()))
- return var + u".metaType()"_s; // We expect the QVariant to be initialized
+ if (m_typeResolver->equals(stored, m_typeResolver->varType())
+ || registerIsStoredIn(content, m_typeResolver->jsPrimitiveType())) {
+ return var + u".metaType()"_s; // We expect the container to be initialized
+ }
if (stored->accessSemantics() == QQmlJSScope::AccessSemantics::Reference)
- return metaTypeFromName(contained);
+ return metaType(contained);
- if (m_typeResolver->registerIsStoredIn(content, m_typeResolver->intType())
- && m_typeResolver->containedType(content)->scopeType() == QQmlJSScope::EnumScope) {
- return metaTypeFromType(m_typeResolver->intType());
- }
+ const QQmlJSScope::ConstPtr nonComposite = QQmlJSScope::nonCompositeBaseType(contained);
+ if (m_typeResolver->isNumeric(stored) && nonComposite->scopeType() == QQmlSA::ScopeType::EnumScope)
+ return metaTypeFromType(nonComposite->baseType());
- if (stored->isListProperty() && m_typeResolver->containedType(content)->isListProperty())
- return metaTypeFromType(m_typeResolver->listPropertyType());
+ if (stored->isListProperty() && contained->isListProperty())
+ return metaType(contained);
- reject(u"content type of non-QVariant wrapper type "_s + content.descriptiveName());
+ reject(u"content type of unsupported wrapper type "_s + content.descriptiveName());
return QString();
}
@@ -2221,32 +2972,28 @@ void QQmlJSCodeGenerator::generate_CmpEqInt(int lhsConst)
{
INJECT_TRACE_INFO(generate_CmpEqInt);
- m_body += m_state.accumulatorVariableOut + u" = "_s;
- m_body += conversion(m_typeResolver->boolType(), m_state.accumulatorOut().storedType(),
- eqIntExpression(lhsConst)) + u";\n"_s;
- generateOutputVariantConversion(m_typeResolver->boolType());
+ generateEqualityOperation(
+ global(m_typeResolver->int32Type()), QString::number(lhsConst), u"equals"_s, false);
}
void QQmlJSCodeGenerator::generate_CmpNeInt(int lhsConst)
{
INJECT_TRACE_INFO(generate_CmpNeInt);
- m_body += m_state.accumulatorVariableOut + u" = "_s;
- m_body += conversion(m_typeResolver->boolType(), m_state.accumulatorOut().storedType(),
- u"!("_s + eqIntExpression(lhsConst) + u')') + u";\n"_s;
- generateOutputVariantConversion(m_typeResolver->boolType());
+ generateEqualityOperation(
+ global(m_typeResolver->int32Type()), QString::number(lhsConst), u"equals"_s, true);
}
void QQmlJSCodeGenerator::generate_CmpEq(int lhs)
{
INJECT_TRACE_INFO(generate_CmpEq);
- generateEqualityOperation(lhs, u"equals"_s, false);
+ generateEqualityOperation(registerType(lhs), registerVariable(lhs), u"equals"_s, false);
}
void QQmlJSCodeGenerator::generate_CmpNe(int lhs)
{
INJECT_TRACE_INFO(generate_CmpNe);
- generateEqualityOperation(lhs, u"equals"_s, true);
+ generateEqualityOperation(registerType(lhs), registerVariable(lhs), u"equals"_s, true);
}
void QQmlJSCodeGenerator::generate_CmpGt(int lhs)
@@ -2276,13 +3023,13 @@ void QQmlJSCodeGenerator::generate_CmpLe(int lhs)
void QQmlJSCodeGenerator::generate_CmpStrictEqual(int lhs)
{
INJECT_TRACE_INFO(generate_CmpStrictEqual);
- generateEqualityOperation(lhs, u"strictlyEquals"_s, false);
+ generateEqualityOperation(registerType(lhs), registerVariable(lhs), u"strictlyEquals"_s, false);
}
void QQmlJSCodeGenerator::generate_CmpStrictNotEqual(int lhs)
{
INJECT_TRACE_INFO(generate_CmpStrictNotEqual);
- generateEqualityOperation(lhs, u"strictlyEquals"_s, true);
+ generateEqualityOperation(registerType(lhs), registerVariable(lhs), u"strictlyEquals"_s, true);
}
void QQmlJSCodeGenerator::generate_CmpIn(int lhs)
@@ -2302,22 +3049,69 @@ void QQmlJSCodeGenerator::generate_As(int lhs)
INJECT_TRACE_INFO(generate_As);
const QString input = registerVariable(lhs);
- const QQmlJSScope::ConstPtr contained
- = m_typeResolver->containedType(m_state.readRegister(lhs));
+ const QQmlJSRegisterContent inputContent = m_state.readRegister(lhs);
+ const QQmlJSRegisterContent outputContent = m_state.accumulatorOut();
+
+ // If the original output is a conversion, we're supposed to check for the contained
+ // type and if it doesn't match, set the result to null or undefined.
+ const QQmlJSRegisterContent originalContent = original(outputContent);
+ const QQmlJSScope::ConstPtr target = originalContent.containedType()->isReferenceType()
+ ? originalContent.containedType()
+ : m_typeResolver->extractNonVoidFromOptionalType(originalContent);
+
+ if (!target) {
+ reject(u"type assertion to unknown type"_s);
+ return;
+ }
+
+ const bool isTrivial = m_typeResolver->inherits(
+ m_typeResolver->originalContainedType(inputContent), target);
m_body += m_state.accumulatorVariableOut + u" = "_s;
- if (m_typeResolver->equals(
- m_state.accumulatorIn().storedType(), m_typeResolver->metaObjectType())
- && contained->isComposite()) {
- m_body += conversion(
- m_typeResolver->genericType(contained), m_state.accumulatorOut().storedType(),
- m_state.accumulatorVariableIn + u"->cast("_s + input + u')');
- } else {
- m_body += conversion(
- m_typeResolver->genericType(contained), m_state.accumulatorOut().storedType(),
- u'(' + metaObject(contained) + u")->cast("_s + input + u')');
+
+ if (!isTrivial && target->isReferenceType()) {
+ const QQmlJSScope::ConstPtr genericContained = m_typeResolver->genericType(target);
+ const QString inputConversion = inputContent.storedType()->isReferenceType()
+ ? input
+ : convertStored(inputContent.storedType(), genericContained, input);
+
+ if (target->isComposite() && m_typeResolver->equals(
+ m_state.accumulatorIn().storedType(), m_typeResolver->metaObjectType())) {
+ m_body += conversion(
+ genericContained, outputContent,
+ m_state.accumulatorVariableIn + u"->cast("_s + inputConversion + u')');
+ } else {
+ m_body += conversion(
+ genericContained, outputContent,
+ u'(' + metaObject(target) + u")->cast("_s + inputConversion + u')');
+ }
+ m_body += u";\n"_s;
+ return;
}
- m_body += u";\n"_s;
+
+ if (registerIsStoredIn(inputContent, m_typeResolver->varType())
+ || registerIsStoredIn(inputContent, m_typeResolver->jsPrimitiveType())) {
+
+ const auto source = m_typeResolver->extractNonVoidFromOptionalType(
+ original(inputContent));
+
+ if (source && m_typeResolver->equals(source, target)) {
+ m_body += input + u".metaType() == "_s + metaType(target)
+ + u" ? " + conversion(inputContent, outputContent, input)
+ + u" : " + conversion(
+ global(m_typeResolver->voidType()), outputContent, QString());
+ m_body += u";\n"_s;
+ return;
+ }
+ }
+
+ if (isTrivial) {
+ // No actual conversion necessary. The 'as' is a no-op
+ m_body += conversion(inputContent, m_state.accumulatorOut(), input) + u";\n"_s;
+ return;
+ }
+
+ reject(u"non-trivial value type assertion"_s);
}
void QQmlJSCodeGenerator::generate_UNot()
@@ -2447,13 +3241,12 @@ void QQmlJSCodeGenerator::generate_Exp(int lhs)
Q_ASSERT(m_error->isValid() || !lhsString.isEmpty());
Q_ASSERT(m_error->isValid() || !rhsString.isEmpty());
- const QQmlJSRegisterContent originalOut = m_typeResolver->original(m_state.accumulatorOut());
+ const QQmlJSRegisterContent originalOut = original(m_state.accumulatorOut());
m_body += m_state.accumulatorVariableOut + u" = "_s;
m_body += conversion(
originalOut, m_state.accumulatorOut(),
u"QQmlPrivate::jsExponentiate("_s + lhsString + u", "_s + rhsString + u')');
m_body += u";\n"_s;
- generateOutputVariantConversion(m_typeResolver->containedType(originalOut));
}
void QQmlJSCodeGenerator::generate_Mul(int lhs)
@@ -2472,10 +3265,10 @@ void QQmlJSCodeGenerator::generate_Mod(int lhs)
{
INJECT_TRACE_INFO(generate_Mod);
- const auto lhsVar = conversion(
+ const auto lhsVar = convertStored(
registerType(lhs).storedType(), m_typeResolver->jsPrimitiveType(),
consumedRegisterVariable(lhs));
- const auto rhsVar = conversion(
+ const auto rhsVar = convertStored(
m_state.accumulatorIn().storedType(), m_typeResolver->jsPrimitiveType(),
consumedAccumulatorVariableIn());
Q_ASSERT(m_error->isValid() || !lhsVar.isEmpty());
@@ -2483,11 +3276,9 @@ void QQmlJSCodeGenerator::generate_Mod(int lhs)
m_body += m_state.accumulatorVariableOut;
m_body += u" = "_s;
- m_body += conversion(m_typeResolver->jsPrimitiveType(), m_state.accumulatorOut().storedType(),
+ m_body += conversion(m_typeResolver->jsPrimitiveType(), m_state.accumulatorOut(),
u'(' + lhsVar + u" % "_s + rhsVar + u')');
m_body += u";\n"_s;
-
- generateOutputVariantConversion(m_typeResolver->jsPrimitiveType());
}
void QQmlJSCodeGenerator::generate_Sub(int lhs)
@@ -2514,40 +3305,19 @@ void QQmlJSCodeGenerator::generate_GetTemplateObject(int index)
BYTECODE_UNIMPLEMENTED();
}
-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 QQmlJSCodeGenerator::startInstruction(
QV4::Moth::Instr::Type type)
{
- m_state.State::operator=(nextStateFromAnnotations(m_state, *m_annotations));
+ m_state.State::operator=(nextStateFromAnnotations(m_state, m_annotations));
const auto accumulatorIn = m_state.registers.find(Accumulator);
if (accumulatorIn != m_state.registers.end()
&& isTypeStorable(m_typeResolver, accumulatorIn.value().content.storedType())) {
- m_state.accumulatorVariableIn = m_registerVariables.value(Accumulator)
- .value(accumulatorIn.value().content.storedType());
+ const QQmlJSRegisterContent &content = accumulatorIn.value().content;
+ m_state.accumulatorVariableIn = m_registerVariables.value(RegisterVariablesKey {
+ content.storedType()->internalName(),
+ Accumulator,
+ content.resultLookupIndex()
+ }).variableName;
Q_ASSERT(!m_state.accumulatorVariableIn.isEmpty());
} else {
m_state.accumulatorVariableIn.clear();
@@ -2574,21 +3344,12 @@ QV4::Moth::ByteCodeHandler::Verdict QQmlJSCodeGenerator::startInstruction(
|| !m_state.accumulatorVariableOut.isEmpty()
|| !isTypeStorable(m_typeResolver, m_state.changedRegister().storedType()));
- const int currentLine = currentSourceLocation().startLine;
- if (currentLine != m_lastLineNumberUsed) {
- const int nextLine = nextJSLine(currentLine);
- for (auto line = currentLine - 1; line < nextLine - 1; ++line) {
- m_body += u"// "_s;
- m_body += m_sourceCodeLines.value(line).trimmed();
- m_body += u'\n';
- }
- m_lastLineNumberUsed = currentLine;
- }
-
// If the instruction has no side effects and doesn't write any register, it's dead.
// We might still need the label, though, and the source code comment.
- if (!m_state.hasSideEffects() && changedRegisterVariable().isEmpty())
+ if (!m_state.hasSideEffects() && changedRegisterVariable().isEmpty()) {
+ generateJumpCodeWithTypeConversions(0);
return SkipInstruction;
+ }
return ProcessInstruction;
}
@@ -2607,86 +3368,225 @@ void QQmlJSCodeGenerator::generateSetInstructionPointer()
void QQmlJSCodeGenerator::generateExceptionCheck()
{
- m_body += u"if (aotContext->engine->hasError())\n"_s;
- m_body += u" return "_s + errorReturnValue() + u";\n"_s;
+ m_body += u"if (aotContext->engine->hasError()) {\n"_s;
+ generateReturnError();
+ m_body += u"}\n"_s;
}
-void QQmlJSCodeGenerator::generateEqualityOperation(int lhs, const QString &function, bool invert)
+void QQmlJSCodeGenerator::generateEqualityOperation(
+ const QQmlJSRegisterContent &lhsContent, const QQmlJSRegisterContent &rhsContent,
+ const QString &lhsName, const QString &rhsName, const QString &function, bool invert)
{
- const QQmlJSRegisterContent lhsContent = registerType(lhs);
- const bool strictlyComparableWithVar = function == "strictlyEquals"_L1
- && canStrictlyCompareWithVar(m_typeResolver, lhsContent, m_state.accumulatorIn());
+ const bool lhsIsOptional = m_typeResolver->isOptionalType(lhsContent);
+ const bool rhsIsOptional = m_typeResolver->isOptionalType(rhsContent);
+
+ const auto rhsContained = rhsIsOptional
+ ? m_typeResolver->extractNonVoidFromOptionalType(rhsContent)
+ : rhsContent.containedType();
+
+ const auto lhsContained = lhsIsOptional
+ ? m_typeResolver->extractNonVoidFromOptionalType(lhsContent)
+ : lhsContent.containedType();
+
+ const bool isStrict = function == "strictlyEquals"_L1;
+ const bool strictlyComparableWithVar
+ = isStrict && canStrictlyCompareWithVar(m_typeResolver, lhsContained, rhsContained);
auto isComparable = [&]() {
- if (m_typeResolver->isPrimitive(lhsContent)
- && m_typeResolver->isPrimitive(m_state.accumulatorIn())) {
+ if (m_typeResolver->isPrimitive(lhsContent) && m_typeResolver->isPrimitive(rhsContent))
return true;
- }
- if (m_typeResolver->isNumeric(lhsContent) && m_state.accumulatorIn().isEnumeration())
+ if (m_typeResolver->isNumeric(lhsContent) && rhsContent.isEnumeration())
return true;
- if (m_typeResolver->isNumeric(m_state.accumulatorIn()) && lhsContent.isEnumeration())
+ if (m_typeResolver->isNumeric(rhsContent) && lhsContent.isEnumeration())
return true;
if (strictlyComparableWithVar)
return true;
- if (canCompareWithQObject(m_typeResolver, lhsContent, m_state.accumulatorIn()))
+ if (canCompareWithQObject(m_typeResolver, lhsContained, rhsContained))
+ return true;
+ if (canCompareWithQUrl(m_typeResolver, lhsContained, rhsContained))
return true;
return false;
};
+ const auto retrieveOriginal = [this](const QQmlJSRegisterContent &content) {
+ const auto contained = content.containedType();
+ const auto originalContent = original(content);
+ const auto containedOriginal = originalContent.containedType();
+
+ if (m_typeResolver->equals(
+ m_typeResolver->genericType(containedOriginal), originalContent.storedType())) {
+ // The original type doesn't need any wrapping.
+ return originalContent;
+ } else if (m_typeResolver->equals(contained, containedOriginal)) {
+ if (originalContent.isConversion()) {
+ // The original conversion origins are more accurate
+ return originalContent.storedIn(content.storedType());
+ }
+ } else if (m_typeResolver->canHold(contained, containedOriginal)) {
+ return originalContent.storedIn(content.storedType());
+ }
+
+ return content;
+ };
+
if (!isComparable()) {
- reject(u"incomparable types %1 and %2"_s.arg(m_state.accumulatorIn().descriptiveName(),
- lhsContent.descriptiveName()));
+ QQmlJSRegisterContent lhsOriginal = retrieveOriginal(lhsContent);
+ QQmlJSRegisterContent rhsOriginal = retrieveOriginal(rhsContent);
+ if (lhsOriginal != lhsContent || rhsOriginal != rhsContent) {
+ // If either side is simply a wrapping of a specific type into a more general one, we
+ // can compare the original types instead. You can't nest wrappings after all.
+ generateEqualityOperation(lhsOriginal, rhsOriginal,
+ conversion(lhsContent.storedType(), lhsOriginal, lhsName),
+ conversion(rhsContent.storedType(), rhsOriginal, rhsName),
+ function, invert);
+ return;
+ }
+
+ reject(u"incomparable types %1 and %2"_s.arg(
+ rhsContent.descriptiveName(), lhsContent.descriptiveName()));
}
const QQmlJSScope::ConstPtr lhsType = lhsContent.storedType();
- const QQmlJSScope::ConstPtr rhsType = m_state.accumulatorIn().storedType();
+ const QQmlJSScope::ConstPtr rhsType = rhsContent.storedType();
- const auto primitive = m_typeResolver->jsPrimitiveType();
- if (m_typeResolver->equals(lhsType, rhsType) && !m_typeResolver->equals(lhsType, primitive)) {
- m_body += m_state.accumulatorVariableOut + u" = "_s;
- if (isTypeStorable(m_typeResolver, lhsType)) {
- m_body += conversion(m_typeResolver->boolType(), m_state.accumulatorOut().storedType(),
- consumedRegisterVariable(lhs) + (invert ? u" != "_s : u" == "_s)
- + consumedAccumulatorVariableIn());
- } else if (m_typeResolver->equals(lhsType, m_typeResolver->emptyListType())) {
- // We cannot compare two empty lists, because we don't know whether it's
- // the same instance or not. "[] === []" is false, but "var a = []; a === a" is true;
- reject(u"comparison of two empty lists"_s);
- } else {
- // null === null and undefined === undefined
- m_body += invert ? u"false"_s : u"true"_s;
- }
- } else if (strictlyComparableWithVar) {
+ if (strictlyComparableWithVar) {
// Determine which side is holding a storable type
- if (const auto registerVariableName = registerVariable(lhs);
- !registerVariableName.isEmpty()) {
- // lhs register holds var type
- generateVariantEqualityComparison(m_state.accumulatorIn(), registerVariableName,
- invert);
- } else {
+ if (!lhsName.isEmpty() && rhsName.isEmpty()) {
+ // lhs register holds var type and rhs is not storable
+ generateVariantEqualityComparison(rhsContent, lhsName, invert);
+ return;
+ }
+
+ if (!rhsName.isEmpty() && lhsName.isEmpty()) {
// lhs content is not storable and rhs is var type
- generateVariantEqualityComparison(lhsContent, m_state.accumulatorVariableIn, invert);
+ generateVariantEqualityComparison(lhsContent, rhsName, invert);
+ return;
}
- } else if (canCompareWithQObject(m_typeResolver, lhsContent, m_state.accumulatorIn())) {
- m_body += m_state.accumulatorVariableOut + u" = "_s;
- m_body += u'('
- + (isTypeStorable(m_typeResolver, lhsContent.storedType())
- ? registerVariable(lhs)
- : m_state.accumulatorVariableIn)
- + (invert ? u" != "_s : u" == "_s) + u"nullptr)"_s;
- } else {
- m_body += m_state.accumulatorVariableOut + u" = "_s;
- m_body += conversion(
- m_typeResolver->boolType(), m_state.accumulatorOut().storedType(),
- (invert ? u"!"_s : QString())
- + conversion(registerType(lhs).storedType(), primitive,
- consumedRegisterVariable(lhs))
- + u'.' + function + u'(' + conversion(
- m_state.accumulatorIn().storedType(), primitive,
- consumedAccumulatorVariableIn())
- + u')');
+
+ if (m_typeResolver->registerContains(lhsContent, m_typeResolver->varType())) {
+ generateVariantEqualityComparison(rhsContent, rhsName, lhsName, invert);
+ return;
+ }
+
+ if (m_typeResolver->registerContains(rhsContent, m_typeResolver->varType())) {
+ generateVariantEqualityComparison(lhsContent, lhsName, rhsName, invert);
+ return;
+ }
+
+ // It shouldn't be possible to get here because optional null should be stored in
+ // QJSPrimitiveValue, not in QVariant. But let's rather be safe than sorry.
+ reject(u"comparison of optional null"_s);
}
+
+ const auto comparison = [&]() -> QString {
+ const auto primitive = m_typeResolver->jsPrimitiveType();
+ const QString sign = invert ? u" != "_s : u" == "_s;
+
+ if (m_typeResolver->equals(lhsType, rhsType)
+ && !m_typeResolver->equals(lhsType, primitive)
+ && !m_typeResolver->equals(lhsType, m_typeResolver->varType())) {
+
+ // Straight forward comparison of equal types,
+ // except QJSPrimitiveValue which has two comparison functions.
+
+ if (isTypeStorable(m_typeResolver, lhsType))
+ return lhsName + sign + rhsName;
+
+ // null === null and undefined === undefined
+ return invert ? u"false"_s : u"true"_s;
+ }
+
+ if (canCompareWithQObject(m_typeResolver, lhsType, rhsType)) {
+ // Comparison of QObject-derived with nullptr or different QObject-derived.
+ return (isTypeStorable(m_typeResolver, lhsType) ? lhsName : u"nullptr"_s)
+ + sign
+ + (isTypeStorable(m_typeResolver, rhsType) ? rhsName : u"nullptr"_s);
+ }
+
+ if (canCompareWithQObject(m_typeResolver, lhsContained, rhsContained)) {
+ // Comparison of optional QObject-derived with nullptr or different QObject-derived.
+ // Mind that null == undefined but null !== undefined
+ // Therefore the isStrict dance.
+
+ QString result;
+ if (isStrict) {
+ if (lhsIsOptional) {
+ if (rhsIsOptional) {
+ // If both are invalid we're fine
+ result += u"(!"_s
+ + lhsName + u".isValid() && !"_s
+ + rhsName + u".isValid()) || "_s;
+ }
+
+ result += u'(' + lhsName + u".isValid() && "_s;
+ } else {
+ result += u'(';
+ }
+
+ if (rhsIsOptional) {
+ result += rhsName + u".isValid() && "_s;
+ }
+ } else {
+ result += u'(';
+ }
+
+ // We do not implement comparison with explicit undefined, yet. Only with null.
+ Q_ASSERT(!m_typeResolver->equals(lhsType, m_typeResolver->voidType()));
+ Q_ASSERT(!m_typeResolver->equals(rhsType, m_typeResolver->voidType()));
+
+ const auto resolvedName = [&](const QString name) -> QString {
+ // If isStrict we check validity already before.
+ const QString content = u"*static_cast<QObject **>("_s + name + u".data())"_s;
+ return isStrict
+ ? content
+ : u'(' + name + u".isValid() ? "_s + content + u" : nullptr)"_s;
+ };
+
+ const QString lhsResolved = lhsIsOptional ? resolvedName(lhsName) : lhsName;
+ const QString rhsResolved = rhsIsOptional ? resolvedName(rhsName) : rhsName;
+
+ return (invert ? u"!("_s : u"("_s) + result
+ + (isTypeStorable(m_typeResolver, lhsType) ? lhsResolved : u"nullptr"_s)
+ + u" == "_s
+ + (isTypeStorable(m_typeResolver, rhsType) ? rhsResolved : u"nullptr"_s)
+ + u"))"_s;
+ }
+
+ if ((m_typeResolver->isUnsignedInteger(rhsType)
+ && m_typeResolver->isUnsignedInteger(lhsType))
+ || (m_typeResolver->isSignedInteger(rhsType)
+ && m_typeResolver->isSignedInteger(lhsType))) {
+ // Both integers of same signedness: Let the C++ compiler perform the type promotion
+ return lhsName + sign + rhsName;
+ }
+
+ if (m_typeResolver->equals(rhsType, m_typeResolver->boolType())
+ && m_typeResolver->isIntegral(lhsType)) {
+ // Integral and bool: We can promote the bool to the integral type
+ return lhsName + sign + convertStored(rhsType, lhsType, rhsName);
+ }
+
+ if (m_typeResolver->equals(lhsType, m_typeResolver->boolType())
+ && m_typeResolver->isIntegral(rhsType)) {
+ // Integral and bool: We can promote the bool to the integral type
+ return convertStored(lhsType, rhsType, lhsName) + sign + rhsName;
+ }
+
+ if (m_typeResolver->isNumeric(lhsType) && m_typeResolver->isNumeric(rhsType)) {
+ // Both numbers: promote them to double
+ return convertStored(lhsType, m_typeResolver->realType(), lhsName)
+ + sign
+ + convertStored(rhsType, m_typeResolver->realType(), rhsName);
+ }
+
+ // If none of the above matches, we have to use QJSPrimitiveValue
+ return (invert ? u"!"_s : QString())
+ + convertStored(lhsType, primitive, lhsName)
+ + u'.' + function + u'(' + convertStored(rhsType, primitive, rhsName) + u')';
+ };
+
+ m_body += m_state.accumulatorVariableOut + u" = "_s;
+ m_body += conversion(m_typeResolver->boolType(), m_state.accumulatorOut(), comparison());
m_body += u";\n"_s;
- generateOutputVariantConversion(m_typeResolver->boolType());
}
void QQmlJSCodeGenerator::generateCompareOperation(int lhs, const QString &cppOperator)
@@ -2696,18 +3596,17 @@ void QQmlJSCodeGenerator::generateCompareOperation(int lhs, const QString &cppOp
const auto lhsType = registerType(lhs);
const QQmlJSScope::ConstPtr compareType =
m_typeResolver->isNumeric(lhsType) && m_typeResolver->isNumeric(m_state.accumulatorIn())
- ? m_typeResolver->merge(lhsType, m_state.accumulatorIn()).storedType()
+ ? m_typeResolver->merge(lhsType.storedType(), m_state.accumulatorIn().storedType())
: m_typeResolver->jsPrimitiveType();
m_body += conversion(
- m_typeResolver->boolType(), m_state.accumulatorOut().storedType(),
- conversion(registerType(lhs).storedType(), compareType,
- consumedRegisterVariable(lhs))
+ m_typeResolver->boolType(), m_state.accumulatorOut(),
+ convertStored(registerType(lhs).storedType(), compareType,
+ consumedRegisterVariable(lhs))
+ u' ' + cppOperator + u' '
- + conversion(m_state.accumulatorIn().storedType(), compareType,
- consumedAccumulatorVariableIn()));
+ + convertStored(m_state.accumulatorIn().storedType(), compareType,
+ consumedAccumulatorVariableIn()));
m_body += u";\n"_s;
- generateOutputVariantConversion(m_typeResolver->boolType());
}
void QQmlJSCodeGenerator::generateArithmeticOperation(int lhs, const QString &cppOperator)
@@ -2736,14 +3635,17 @@ void QQmlJSCodeGenerator::generateArithmeticOperation(
Q_ASSERT(m_error->isValid() || !lhs.isEmpty());
Q_ASSERT(m_error->isValid() || !rhs.isEmpty());
- const QQmlJSRegisterContent originalOut = m_typeResolver->original(m_state.accumulatorOut());
+ const QQmlJSRegisterContent originalOut = original(m_state.accumulatorOut());
m_body += m_state.accumulatorVariableOut;
m_body += u" = "_s;
+ const QString explicitCast
+ = m_typeResolver->equals(originalOut.storedType(), m_typeResolver->stringType())
+ ? originalOut.storedType()->internalName()
+ : QString();
m_body += conversion(
originalOut, m_state.accumulatorOut(),
- u'(' + lhs + u' ' + cppOperator + u' ' + rhs + u')');
+ explicitCast + u'(' + lhs + u' ' + cppOperator + u' ' + rhs + u')');
m_body += u";\n"_s;
- generateOutputVariantConversion(m_typeResolver->containedType(originalOut));
}
void QQmlJSCodeGenerator::generateArithmeticConstOperation(int rhsConst, const QString &cppOperator)
@@ -2751,14 +3653,15 @@ void QQmlJSCodeGenerator::generateArithmeticConstOperation(int rhsConst, const Q
generateArithmeticOperation(
conversion(m_state.accumulatorIn(), m_state.readAccumulator(),
consumedAccumulatorVariableIn()),
- conversion(m_typeResolver->globalType(m_typeResolver->intType()),
+ conversion(global(m_typeResolver->int32Type()),
m_state.readAccumulator(), QString::number(rhsConst)),
cppOperator);
}
void QQmlJSCodeGenerator::generateUnaryOperation(const QString &cppOperator)
{
- const auto var = conversion(m_state.accumulatorIn(), m_state.readAccumulator(),
+ const auto var = conversion(m_state.accumulatorIn(),
+ original(m_state.readAccumulator()),
consumedAccumulatorVariableIn());
if (var == m_state.accumulatorVariableOut) {
@@ -2766,8 +3669,8 @@ void QQmlJSCodeGenerator::generateUnaryOperation(const QString &cppOperator)
return;
}
- const auto original = m_typeResolver->original(m_state.accumulatorOut());
- if (m_state.accumulatorOut() == original) {
+ const auto originalResult = original(m_state.accumulatorOut());
+ if (m_state.accumulatorOut() == originalResult) {
m_body += m_state.accumulatorVariableOut + u" = "_s + var + u";\n"_s;
m_body += m_state.accumulatorVariableOut + u" = "_s
+ cppOperator + m_state.accumulatorVariableOut + u";\n"_s;
@@ -2775,10 +3678,7 @@ void QQmlJSCodeGenerator::generateUnaryOperation(const QString &cppOperator)
}
m_body += m_state.accumulatorVariableOut + u" = "_s + conversion(
- m_typeResolver->original(m_state.accumulatorOut()),
- m_state.accumulatorOut(), cppOperator + var) + u";\n"_s;
-
- generateOutputVariantConversion(m_typeResolver->containedType(original));
+ originalResult, m_state.accumulatorOut(), cppOperator + var) + u";\n"_s;
}
void QQmlJSCodeGenerator::generateInPlaceOperation(const QString &cppOperator)
@@ -2796,8 +3696,8 @@ void QQmlJSCodeGenerator::generateInPlaceOperation(const QString &cppOperator)
const QString var = conversion(m_state.accumulatorIn(), m_state.readAccumulator(),
consumedAccumulatorVariableIn());
- const auto original = m_typeResolver->original(m_state.accumulatorOut());
- if (m_state.accumulatorOut() == original) {
+ const auto originalResult = original(m_state.accumulatorOut());
+ if (m_state.accumulatorOut() == originalResult) {
m_body += m_state.accumulatorVariableOut + u" = "_s + var + u";\n"_s;
m_body += cppOperator + m_state.accumulatorVariableOut + u";\n"_s;
return;
@@ -2806,19 +3706,26 @@ void QQmlJSCodeGenerator::generateInPlaceOperation(const QString &cppOperator)
m_body += u"{\n"_s;
m_body += u"auto converted = "_s + var + u";\n"_s;
m_body += m_state.accumulatorVariableOut + u" = "_s + conversion(
- m_typeResolver->original(m_state.accumulatorOut()),
- m_state.accumulatorOut(), u'(' + cppOperator + u"converted)"_s) + u";\n"_s;
+ originalResult, m_state.accumulatorOut(), u'('
+ + cppOperator + u"converted)"_s) + u";\n"_s;
m_body += u"}\n"_s;
- generateOutputVariantConversion(m_typeResolver->containedType(original));
}
void QQmlJSCodeGenerator::generateLookup(const QString &lookup, const QString &initialization,
const QString &resultPreparation)
{
+ m_body += u"#ifndef QT_NO_DEBUG\n"_s;
+ generateSetInstructionPointer();
+ m_body += u"#endif\n"_s;
+
if (!resultPreparation.isEmpty())
m_body += resultPreparation + u";\n"_s;
m_body += u"while (!"_s + lookup + u") {\n"_s;
+
+ m_body += u"#ifdef QT_NO_DEBUG\n"_s;
generateSetInstructionPointer();
+ m_body += u"#endif\n"_s;
+
m_body += initialization + u";\n"_s;
generateExceptionCheck();
if (!resultPreparation.isEmpty())
@@ -2830,43 +3737,51 @@ void QQmlJSCodeGenerator::generateJumpCodeWithTypeConversions(int relativeOffset
{
QString conversionCode;
const int absoluteOffset = nextInstructionOffset() + relativeOffset;
- const auto annotation = m_annotations->find(absoluteOffset);
- if (annotation != m_annotations->constEnd()) {
+ const auto annotation = m_annotations.find(absoluteOffset);
+ if (static_cast<InstructionAnnotations::const_iterator>(annotation) != m_annotations.constEnd()) {
const auto &conversions = annotation->second.typeConversions;
for (auto regIt = conversions.constBegin(), regEnd = conversions.constEnd();
regIt != regEnd; ++regIt) {
- int registerIndex = regIt.key();
const QQmlJSRegisterContent targetType = regIt.value().content;
- if (!targetType.isValid())
+ if (!targetType.isValid() || !isTypeStorable(m_typeResolver, targetType.storedType()))
+ continue;
+
+ const int registerIndex = regIt.key();
+ const auto variable = m_registerVariables.constFind(RegisterVariablesKey {
+ targetType.storedType()->internalName(),
+ registerIndex,
+ targetType.resultLookupIndex()
+ });
+
+ if (variable == m_registerVariables.constEnd())
continue;
QQmlJSRegisterContent currentType;
QString currentVariable;
if (registerIndex == m_state.changedRegisterIndex()) {
- currentType = m_state.changedRegister();
currentVariable = changedRegisterVariable();
+ if (variable->variableName == currentVariable)
+ continue;
+
+ currentType = m_state.changedRegister();
+ currentVariable = u"std::move("_s + currentVariable + u')';
} else {
- auto it = m_state.registers.find(registerIndex);
- if (it == m_state.registers.end())
+ const auto it = m_state.registers.find(registerIndex);
+ if (it == m_state.registers.end()
+ || variable->variableName == registerVariable(registerIndex)) {
continue;
+ }
+
currentType = it.value().content;
currentVariable = consumedRegisterVariable(registerIndex);
}
// Actually == here. We want the jump code also for equal types
- if (currentType == targetType
- || !isTypeStorable(m_typeResolver, targetType.storedType())) {
+ if (currentType == targetType)
continue;
- }
- Q_ASSERT(m_registerVariables.contains(registerIndex));
- const auto &currentRegisterVariables = m_registerVariables[registerIndex];
- const auto variable = currentRegisterVariables.constFind(targetType.storedType());
- if (variable == currentRegisterVariables.end() || *variable == currentVariable)
- continue;
-
- conversionCode += *variable;
+ conversionCode += variable->variableName;
conversionCode += u" = "_s;
conversionCode += conversion(currentType, targetType, currentVariable);
conversionCode += u";\n"_s;
@@ -2880,41 +3795,60 @@ void QQmlJSCodeGenerator::generateJumpCodeWithTypeConversions(int relativeOffset
conversionCode += u" goto "_s + *labelIt + u";\n"_s;
}
- if (!conversionCode.isEmpty())
- m_body += u"{\n"_s + conversionCode + u"}\n"_s;
+ m_body += u"{\n"_s + conversionCode + u"}\n"_s;
}
QString QQmlJSCodeGenerator::registerVariable(int index) const
{
- auto it = m_registerVariables.find(index);
- if (it != m_registerVariables.end()) {
- const QString variable = it->value(registerType(index).storedType());
- if (!variable.isEmpty())
- return variable;
- }
+ const QQmlJSRegisterContent &content = registerType(index);
+ const auto it = m_registerVariables.constFind(RegisterVariablesKey {
+ content.storedType()->internalName(),
+ index,
+ content.resultLookupIndex()
+ });
+ if (it != m_registerVariables.constEnd())
+ return it->variableName;
return QString();
}
+QString QQmlJSCodeGenerator::lookupVariable(int lookupIndex) const
+{
+ for (auto it = m_registerVariables.constBegin(), end = m_registerVariables.constEnd(); it != end; ++it) {
+ if (it.key().lookupIndex == lookupIndex)
+ return it->variableName;
+ }
+ return QString();
+}
+
QString QQmlJSCodeGenerator::consumedRegisterVariable(int index) const
{
const QString var = registerVariable(index);
- if (var.isEmpty() || !m_state.canMoveReadRegister(index))
+ if (var.isEmpty() || !shouldMoveRegister(index))
return var;
return u"std::move(" + var + u")";
}
QString QQmlJSCodeGenerator::consumedAccumulatorVariableIn() const
{
- return m_state.canMoveReadRegister(Accumulator)
+ return shouldMoveRegister(Accumulator)
? u"std::move(" + m_state.accumulatorVariableIn + u")"
: m_state.accumulatorVariableIn;
}
QString QQmlJSCodeGenerator::changedRegisterVariable() const
{
- return m_registerVariables.value(m_state.changedRegisterIndex()).value(
- m_state.changedRegister().storedType());
+ const QQmlJSRegisterContent &changedRegister = m_state.changedRegister();
+
+ const QQmlJSScope::ConstPtr storedType = changedRegister.storedType();
+ if (storedType.isNull())
+ return QString();
+
+ return m_registerVariables.value(RegisterVariablesKey {
+ storedType->internalName(),
+ m_state.changedRegisterIndex(),
+ changedRegister.resultLookupIndex()
+ }).variableName;
}
QQmlJSRegisterContent QQmlJSCodeGenerator::registerType(int index) const
@@ -2926,9 +3860,71 @@ QQmlJSRegisterContent QQmlJSCodeGenerator::registerType(int index) const
return QQmlJSRegisterContent();
}
-QString QQmlJSCodeGenerator::conversion(const QQmlJSScope::ConstPtr &from,
- const QQmlJSScope::ConstPtr &to,
- const QString &variable)
+QQmlJSRegisterContent QQmlJSCodeGenerator::lookupType(int lookupIndex) const
+{
+ auto it = m_state.lookups.find(lookupIndex);
+ if (it != m_state.lookups.end())
+ return it.value().content;
+
+ return QQmlJSRegisterContent();
+}
+
+bool QQmlJSCodeGenerator::shouldMoveRegister(int index) const
+{
+ return m_state.canMoveReadRegister(index)
+ && !m_typeResolver->isTriviallyCopyable(m_state.readRegister(index).storedType());
+}
+
+QString QQmlJSCodeGenerator::conversion(
+ const QQmlJSRegisterContent &from, const QQmlJSRegisterContent &to, const QString &variable)
+{
+ const QQmlJSScope::ConstPtr contained = to.containedType();
+
+ // If from is QJSPrimitiveValue and to contains a primitive we coerce using QJSPrimitiveValue
+ if (registerIsStoredIn(from, m_typeResolver->jsPrimitiveType())
+ && m_typeResolver->isPrimitive(to)) {
+
+ QString primitive = [&]() {
+ if (m_typeResolver->equals(contained, m_typeResolver->jsPrimitiveType()))
+ return variable;
+
+ const QString conversion = variable + u".to<QJSPrimitiveValue::%1>()"_s;
+ if (m_typeResolver->equals(contained, m_typeResolver->boolType()))
+ return conversion.arg(u"Boolean"_s);
+ if (m_typeResolver->isIntegral(to))
+ return conversion.arg(u"Integer"_s);
+ if (m_typeResolver->isNumeric(to))
+ return conversion.arg(u"Double"_s);
+ if (m_typeResolver->equals(contained, m_typeResolver->stringType()))
+ return conversion.arg(u"String"_s);
+ reject(u"Conversion of QJSPrimitiveValue to "_s + contained->internalName());
+ return QString();
+ }();
+
+ if (primitive.isEmpty())
+ return primitive;
+
+ return convertStored(m_typeResolver->jsPrimitiveType(), to.storedType(), primitive);
+ }
+
+ if (registerIsStoredIn(to, contained)
+ || m_typeResolver->isNumeric(to.storedType())
+ || to.storedType()->isReferenceType()
+ || m_typeResolver->registerContains(from, contained)) {
+ // If:
+ // * the output is not actually wrapped at all, or
+ // * the output is stored in a numeric type (as there are no internals to a number), or
+ // * the output is a QObject pointer, or
+ // * we merely wrap the value into a new container,
+ // we can convert by stored type.
+ return convertStored(from.storedType(), to.storedType(), variable);
+ } else {
+ return convertContained(from, to, variable);
+ }
+}
+
+QString QQmlJSCodeGenerator::convertStored(
+ const QQmlJSScope::ConstPtr &from, const QQmlJSScope::ConstPtr &to, const QString &variable)
{
// TODO: most values can be moved, which is much more efficient with the common types.
// add a move(from, to, variable) function that implements the moves.
@@ -2939,32 +3935,36 @@ QString QQmlJSCodeGenerator::conversion(const QQmlJSScope::ConstPtr &from,
const auto jsPrimitiveType = m_typeResolver->jsPrimitiveType();
const auto boolType = m_typeResolver->boolType();
- auto zeroBoolOrNumeric = [&](const QQmlJSScope::ConstPtr &to) {
+ auto zeroBoolOrInt = [&](const QQmlJSScope::ConstPtr &to) {
if (m_typeResolver->equals(to, boolType))
return u"false"_s;
- if (m_typeResolver->equals(to, m_typeResolver->intType()))
+ if (m_typeResolver->isSignedInteger(to))
return u"0"_s;
- if (m_typeResolver->equals(to, m_typeResolver->uintType()))
+ if (m_typeResolver->isUnsignedInteger(to))
return u"0u"_s;
- if (m_typeResolver->equals(to, m_typeResolver->floatType()))
- return u"0.0f"_s;
- if (m_typeResolver->equals(to, m_typeResolver->realType()))
- return u"0.0"_s;
return QString();
};
if (m_typeResolver->equals(from, m_typeResolver->voidType())) {
if (to->accessSemantics() == QQmlJSScope::AccessSemantics::Reference)
return u"static_cast<"_s + to->internalName() + u" *>(nullptr)"_s;
- const QString zero = zeroBoolOrNumeric(to);
+ const QString zero = zeroBoolOrInt(to);
if (!zero.isEmpty())
return zero;
+ if (m_typeResolver->equals(to, m_typeResolver->floatType()))
+ return u"std::numeric_limits<float>::quiet_NaN()"_s;
+ if (m_typeResolver->equals(to, m_typeResolver->realType()))
+ return u"std::numeric_limits<double>::quiet_NaN()"_s;
if (m_typeResolver->equals(to, m_typeResolver->stringType()))
return QQmlJSUtils::toLiteral(u"undefined"_s);
+ if (m_typeResolver->equals(to, m_typeResolver->varType()))
+ return u"QVariant()"_s;
+ if (m_typeResolver->equals(to, m_typeResolver->jsValueType()))
+ return u"QJSValue();"_s;
+ if (m_typeResolver->equals(to, m_typeResolver->jsPrimitiveType()))
+ return u"QJSPrimitiveValue()"_s;
if (m_typeResolver->equals(from, to))
return QString();
- // Anything else is just the default constructed type.
- return to->augmentedInternalName() + u"()"_s;
}
if (m_typeResolver->equals(from, m_typeResolver->nullType())) {
@@ -2976,9 +3976,13 @@ QString QQmlJSCodeGenerator::conversion(const QQmlJSScope::ConstPtr &from,
return u"QJSPrimitiveValue(QJSPrimitiveNull())"_s;
if (m_typeResolver->equals(to, varType))
return u"QVariant::fromValue<std::nullptr_t>(nullptr)"_s;
- const QString zero = zeroBoolOrNumeric(to);
+ const QString zero = zeroBoolOrInt(to);
if (!zero.isEmpty())
return zero;
+ if (m_typeResolver->equals(to, m_typeResolver->floatType()))
+ return u"0.0f"_s;
+ if (m_typeResolver->equals(to, m_typeResolver->realType()))
+ return u"0.0"_s;
if (m_typeResolver->equals(to, m_typeResolver->stringType()))
return QQmlJSUtils::toLiteral(u"null"_s);
if (m_typeResolver->equals(from, to))
@@ -2986,16 +3990,6 @@ QString QQmlJSCodeGenerator::conversion(const QQmlJSScope::ConstPtr &from,
reject(u"Conversion from null to %1"_s.arg(to->internalName()));
}
- if (m_typeResolver->equals(from, m_typeResolver->emptyListType())) {
- if (to->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence)
- return castTargetName(to) + u"()"_s;
- if (m_typeResolver->equals(to, m_typeResolver->varType()))
- return u"QVariant(QVariantList())"_s;
- if (m_typeResolver->equals(from, to))
- return QString();
- reject(u"Conversion from empty list to %1"_s.arg(to->internalName()));
- }
-
if (m_typeResolver->equals(from, to))
return variable;
@@ -3027,19 +4021,19 @@ QString QQmlJSCodeGenerator::conversion(const QQmlJSScope::ConstPtr &from,
return variable;
const auto isBoolOrNumber = [&](const QQmlJSScope::ConstPtr &type) {
- return m_typeResolver->isNumeric(m_typeResolver->globalType(type))
+ return m_typeResolver->isNumeric(type)
|| m_typeResolver->equals(type, m_typeResolver->boolType())
- || type->scopeType() == QQmlJSScope::EnumScope;
+ || type->scopeType() == QQmlSA::ScopeType::EnumScope;
};
if (m_typeResolver->equals(from, m_typeResolver->realType())
|| m_typeResolver->equals(from, m_typeResolver->floatType())) {
- if (m_typeResolver->equals(to, m_typeResolver->intType()))
+ if (m_typeResolver->isSignedInteger(to))
return u"QJSNumberCoercion::toInteger("_s + variable + u')';
- if (m_typeResolver->equals(to, m_typeResolver->uintType()))
+ if (m_typeResolver->isUnsignedInteger(to))
return u"uint(QJSNumberCoercion::toInteger("_s + variable + u"))"_s;
if (m_typeResolver->equals(to, m_typeResolver->boolType()))
- return u'(' + variable + u" && !std::isnan("_s + variable + u"))"_s;
+ return u"[](double moved){ return moved && !std::isnan(moved); }("_s + variable + u')';
}
if (isBoolOrNumber(from) && isBoolOrNumber(to))
@@ -3051,10 +4045,12 @@ QString QQmlJSCodeGenerator::conversion(const QQmlJSScope::ConstPtr &from,
return variable + u".toDouble()"_s;
if (m_typeResolver->equals(to, boolType))
return variable + u".toBoolean()"_s;
- if (m_typeResolver->equals(to, m_typeResolver->intType()))
- return variable + u".toInteger()"_s;
- if (m_typeResolver->equals(to, m_typeResolver->uintType()))
- return u"uint("_s + variable + u".toInteger())"_s;
+ if (m_typeResolver->equals(to, m_typeResolver->int64Type())
+ || m_typeResolver->equals(to, m_typeResolver->uint64Type())) {
+ return u"%1(%2.toDouble())"_s.arg(to->internalName(), variable);
+ }
+ if (m_typeResolver->isIntegral(to))
+ return u"%1(%2.toInteger())"_s.arg(to->internalName(), variable);
if (m_typeResolver->equals(to, m_typeResolver->stringType()))
return variable + u".toString()"_s;
if (m_typeResolver->equals(to, jsValueType))
@@ -3079,10 +4075,15 @@ QString QQmlJSCodeGenerator::conversion(const QQmlJSScope::ConstPtr &from,
Q_ASSERT(!m_typeResolver->equals(from, m_typeResolver->voidType()));
if (m_typeResolver->equals(from, m_typeResolver->boolType())
- || m_typeResolver->equals(from, m_typeResolver->intType())
+ || m_typeResolver->equals(from, m_typeResolver->int32Type())
|| m_typeResolver->equals(from, m_typeResolver->realType())
|| m_typeResolver->equals(from, m_typeResolver->stringType())) {
return u"QJSPrimitiveValue("_s + variable + u')';
+ } else if (m_typeResolver->equals(from, m_typeResolver->int16Type())
+ || m_typeResolver->equals(from, m_typeResolver->int8Type())
+ || m_typeResolver->equals(from, m_typeResolver->uint16Type())
+ || m_typeResolver->equals(from, m_typeResolver->uint8Type())) {
+ return u"QJSPrimitiveValue(int("_s + variable + u"))"_s;
} else if (m_typeResolver->isNumeric(from)) {
return u"QJSPrimitiveValue(double("_s + variable + u"))"_s;
}
@@ -3130,7 +4131,8 @@ QString QQmlJSCodeGenerator::conversion(const QQmlJSScope::ConstPtr &from,
m_typeResolver->dateTimeType(),
m_typeResolver->dateType(),
m_typeResolver->timeType(),
- m_typeResolver->stringType()}) {
+ m_typeResolver->stringType(),
+ m_typeResolver->realType()}) {
if (m_typeResolver->equals(to, targetType)) {
return u"aotContext->engine->coerceValue<%1, %2>(%3)"_s.arg(
originType->internalName(), targetType->internalName(), variable);
@@ -3145,9 +4147,9 @@ QString QQmlJSCodeGenerator::conversion(const QQmlJSScope::ConstPtr &from,
{
if (m_typeResolver->equals(type, m_typeResolver->boolType()))
return expression + u".toBoolean()"_s;
- if (m_typeResolver->equals(type, m_typeResolver->intType()))
+ if (m_typeResolver->isSignedInteger(type))
return expression + u".toInteger()"_s;
- if (m_typeResolver->equals(type, m_typeResolver->uintType()))
+ if (m_typeResolver->isUnsignedInteger(type))
return u"uint("_s + expression + u".toInteger())"_s;
if (m_typeResolver->equals(type, m_typeResolver->realType()))
return expression + u".toDouble()"_s;
@@ -3160,7 +4162,7 @@ QString QQmlJSCodeGenerator::conversion(const QQmlJSScope::ConstPtr &from,
if (!retrieveFromPrimitive(from, u"x"_s).isEmpty()) {
const QString retrieve = retrieveFromPrimitive(
- to, conversion(from, m_typeResolver->jsPrimitiveType(), variable));
+ to, convertStored(from, m_typeResolver->jsPrimitiveType(), variable));
if (!retrieve.isEmpty())
return retrieve;
}
@@ -3170,25 +4172,120 @@ QString QQmlJSCodeGenerator::conversion(const QQmlJSScope::ConstPtr &from,
+ castTargetName(to) + u">("_s + variable + u')';
}
+ // Any value type is a non-null JS 'object' and therefore coerces to true.
+ if (m_typeResolver->equals(to, m_typeResolver->boolType())) {
+ // All the interesting cases are already handled above:
+ Q_ASSERT(!m_typeResolver->equals(from, m_typeResolver->nullType()));
+ Q_ASSERT(!m_typeResolver->equals(from, m_typeResolver->voidType()));
+ Q_ASSERT(retrieveFromPrimitive(from, u"x"_s).isEmpty());
+ Q_ASSERT(!isBoolOrNumber(from));
+
+ return u"true"_s;
+ }
+
+ if (m_typeResolver->areEquivalentLists(from, to))
+ return variable;
+
+ if (from->isListProperty()
+ && to->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence
+ && to->valueType()->isReferenceType()
+ && !to->isListProperty()) {
+ return variable + u".toList<"_s + to->internalName() + u">()"_s;
+ }
+
+ bool isExtension = false;
+ if (m_typeResolver->canPopulate(to, from, &isExtension)) {
+ reject(u"populating "_s + to->internalName() + u" from "_s + from->internalName());
+ } else if (const auto ctor = m_typeResolver->selectConstructor(to, from, &isExtension);
+ ctor.isValid()) {
+ const auto argumentTypes = ctor.parameters();
+ return (isExtension ? to->extensionType().scope->internalName() : to->internalName())
+ + u"("_s + convertStored(from, argumentTypes[0].type(), variable) + u")"_s;
+ }
+
+ if (m_typeResolver->equals(to, m_typeResolver->stringType())
+ && from->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence) {
+ addInclude(u"QtQml/qjslist.h"_s);
+
+ // Extend the life time of whatever variable is across the call to toString().
+ // variable may be an rvalue.
+ return u"[&](auto &&l){ return QJSList(&l, aotContext->engine).toString(); }("_s
+ + variable + u')';
+ }
+
// TODO: add more conversions
reject(u"conversion from "_s + from->internalName() + u" to "_s + to->internalName());
return QString();
}
-int QQmlJSCodeGenerator::nextJSLine(uint line) const
+QString QQmlJSCodeGenerator::convertContained(const QQmlJSRegisterContent &from, const QQmlJSRegisterContent &to, const QString &variable)
{
- auto findLine = [](int line, const QV4::CompiledData::CodeOffsetToLineAndStatement &entry) {
- return entry.line > line;
- };
- const auto codeToLine
- = std::upper_bound(m_context->lineAndStatementNumberMapping.constBegin(),
- m_context->lineAndStatementNumberMapping.constEnd(),
- line,
- findLine);
- bool bNoNextLine = m_context->lineAndStatementNumberMapping.constEnd() == codeToLine;
+ const QQmlJSScope::ConstPtr containedFrom = from.containedType();
+ const QQmlJSScope::ConstPtr containedTo = to.containedType();
+
+ // Those should be handled before, by convertStored().
+ Q_ASSERT(!to.storedType()->isReferenceType());
+ Q_ASSERT(!registerIsStoredIn(to, containedTo));
+ Q_ASSERT(!m_typeResolver->isIntegral(from.storedType()));
+ Q_ASSERT(!m_typeResolver->equals(containedFrom, containedTo));
+
+ if (!registerIsStoredIn(to, m_typeResolver->varType()) &&
+ !registerIsStoredIn(to, m_typeResolver->jsPrimitiveType())) {
+ reject(u"internal conversion into unsupported wrapper type."_s);
+ return QString();
+ }
+
+ bool isExtension = false;
+ if (m_typeResolver->canPopulate(containedTo, containedFrom, &isExtension)) {
+ reject(u"populating "_s + containedTo->internalName()
+ + u" from "_s + containedFrom->internalName());
+ return QString();
+ } else if (const auto ctor = m_typeResolver->selectConstructor(
+ containedTo, containedFrom, &isExtension); ctor.isValid()) {
+ const auto argumentTypes = ctor.parameters();
+ const QQmlJSScope::ConstPtr argumentType = argumentTypes[0].type();
+
+ // We need to store the converted argument in a temporary
+ // because it might not be an lvalue.
+
+ QString input;
+ QString argPointer;
- return static_cast<int>(bNoNextLine ? -1 : codeToLine->line);
+ if (m_typeResolver->equals(argumentType, containedFrom)) {
+ input = variable;
+ argPointer = contentPointer(from, u"arg"_s);
+ } else {
+ const QQmlJSRegisterContent argument = global(argumentType);
+ input = conversion(from, argument, variable);
+ argPointer = contentPointer(argument, u"arg"_s);
+ }
+
+ return u"[&](){ auto arg = " + input
+ + u"; return aotContext->constructValueType("_s + metaType(containedTo)
+ + u", "_s + metaObject(
+ isExtension ? containedTo->extensionType().scope : containedTo)
+ + u", "_s + QString::number(int(ctor.constructorIndex()))
+ + u", "_s + argPointer + u"); }()"_s;
+ }
+
+ const auto originalFrom = original(from);
+ const auto containedOriginalFrom = originalFrom.containedType();
+ if (!m_typeResolver->equals(containedFrom, containedOriginalFrom)
+ && m_typeResolver->canHold(containedFrom, containedOriginalFrom)) {
+ // If from is simply a wrapping of a specific type into a more general one, we can convert
+ // the original type instead. You can't nest wrappings after all.
+ return conversion(originalFrom.storedIn(from.storedType()), to, variable);
+ }
+
+ if (m_typeResolver->isPrimitive(containedFrom) && m_typeResolver->isPrimitive(containedTo)) {
+ const QQmlJSRegisterContent intermediate = from.storedIn(m_typeResolver->jsPrimitiveType());
+ return conversion(intermediate, to, conversion(from, intermediate, variable));
+ }
+
+ reject(u"internal conversion with incompatible or ambiguous types: %1 -> %2"_s
+ .arg(from.descriptiveName(), to.descriptiveName()));
+ return QString();
}
void QQmlJSCodeGenerator::reject(const QString &thing)
@@ -3213,12 +4310,12 @@ QQmlJSCodeGenerator::AccumulatorConverter::AccumulatorConverter(QQmlJSCodeGenera
// If the stored type differs or if we store in QVariant and the contained type differs,
// then we have to use a temporary ...
if (!resolver->equals(origStored, stored)
- || (!resolver->equals(origContained, resolver->containedType(accumulatorOut))
+ || (!resolver->equals(origContained, accumulatorOut.containedType())
&& resolver->equals(stored, resolver->varType()))) {
const bool storable = isTypeStorable(resolver, origStored);
generator->m_state.accumulatorVariableOut = storable ? u"retrieved"_s : QString();
- generator->m_state.setRegister(Accumulator, resolver->original(accumulatorOut));
+ generator->m_state.setRegister(Accumulator, generator->original(accumulatorOut));
generator->m_body += u"{\n"_s;
if (storable) {
generator->m_body += origStored->augmentedInternalName() + u' '
@@ -3226,7 +4323,7 @@ QQmlJSCodeGenerator::AccumulatorConverter::AccumulatorConverter(QQmlJSCodeGenera
}
} else if (generator->m_state.accumulatorVariableIn == generator->m_state.accumulatorVariableOut
&& generator->m_state.readsRegister(Accumulator)
- && resolver->registerIsStoredIn(
+ && generator->registerIsStoredIn(
generator->m_state.accumulatorOut(), resolver->varType())) {
// If both m_state.accumulatorIn and m_state.accumulatorOut are QVariant, we will need to
// prepare the output QVariant, and afterwards use the input variant. Therefore we need to
@@ -3244,13 +4341,10 @@ QQmlJSCodeGenerator::AccumulatorConverter::~AccumulatorConverter()
if (accumulatorVariableOut != generator->m_state.accumulatorVariableOut) {
generator->m_body += accumulatorVariableOut + u" = "_s + generator->conversion(
generator->m_state.accumulatorOut(), accumulatorOut,
- generator->m_state.accumulatorVariableOut) + u";\n"_s;
- const auto contained = generator->m_typeResolver->containedType(
- generator->m_state.accumulatorOut());
+ u"std::move("_s + generator->m_state.accumulatorVariableOut + u')') + u";\n"_s;
generator->m_body += u"}\n"_s;
generator->m_state.setRegister(Accumulator, accumulatorOut);
generator->m_state.accumulatorVariableOut = accumulatorVariableOut;
- generator->generateOutputVariantConversion(contained);
} else if (accumulatorVariableIn != generator->m_state.accumulatorVariableIn) {
generator->m_body += u"}\n"_s;
generator->m_state.accumulatorVariableIn = accumulatorVariableIn;
diff --git a/src/qmlcompiler/qqmljscodegenerator_p.h b/src/qmlcompiler/qqmljscodegenerator_p.h
index 4719764294..ffa2c80975 100644
--- a/src/qmlcompiler/qqmljscodegenerator_p.h
+++ b/src/qmlcompiler/qqmljscodegenerator_p.h
@@ -27,17 +27,17 @@
QT_BEGIN_NAMESPACE
-class Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSCodeGenerator : public QQmlJSCompilePass
+class Q_QMLCOMPILER_EXPORT QQmlJSCodeGenerator : public QQmlJSCompilePass
{
public:
QQmlJSCodeGenerator(const QV4::Compiler::Context *compilerContext,
- const QV4::Compiler::JSUnitGenerator *unitGenerator,
- const QQmlJSTypeResolver *typeResolver,
- QQmlJSLogger *logger, const QStringList &sourceCodeLines);
+ const QV4::Compiler::JSUnitGenerator *unitGenerator,
+ const QQmlJSTypeResolver *typeResolver, QQmlJSLogger *logger,
+ BasicBlocks basicBlocks, InstructionAnnotations annotations);
~QQmlJSCodeGenerator() = default;
- QQmlJSAotFunction run(const Function *function, const InstructionAnnotations *annotations,
- QQmlJS::DiagnosticMessage *error);
+ QQmlJSAotFunction run(const Function *function, QQmlJS::DiagnosticMessage *error,
+ bool basicBlocksValidationFailed);
protected:
struct CodegenState : public State
@@ -49,7 +49,7 @@ protected:
// This is an RAII helper we can use to automatically convert the result of "inflexible"
// operations to the desired type. For example GetLookup can only retrieve the type of
// the property we're looking up. If we want to store a different type, we need to convert.
- struct Q_QMLCOMPILER_PRIVATE_EXPORT AccumulatorConverter
+ struct Q_QMLCOMPILER_EXPORT AccumulatorConverter
{
Q_DISABLE_COPY_MOVE(AccumulatorConverter);
AccumulatorConverter(QQmlJSCodeGenerator *generator);
@@ -63,6 +63,7 @@ protected:
};
virtual QString metaObject(const QQmlJSScope::ConstPtr &objectType);
+ virtual QString metaType(const QQmlJSScope::ConstPtr &type);
void generate_Ret() override;
void generate_Debug() override;
@@ -132,9 +133,9 @@ protected:
void generate_PopScriptContext() override;
void generate_PopContext() override;
void generate_GetIterator(int iterator) override;
- void generate_IteratorNext(int value, int done) override;
- void generate_IteratorNextForYieldStar(int iterator, int object) override;
- void generate_IteratorClose(int done) override;
+ void generate_IteratorNext(int value, int offset) override;
+ void generate_IteratorNextForYieldStar(int iterator, int object, int offset) override;
+ void generate_IteratorClose() override;
void generate_DestructureRestElement() override;
void generate_DeleteProperty(int base, int index) override;
void generate_DeleteName(int name) override;
@@ -210,20 +211,47 @@ protected:
QString conversion(const QQmlJSRegisterContent &from,
const QQmlJSRegisterContent &to,
+ const QString &variable);
+
+ QString conversion(const QQmlJSScope::ConstPtr &from,
+ const QQmlJSRegisterContent &to,
const QString &variable)
{
- return conversion(from.storedType(), to.storedType(), variable);
+ const QQmlJSScope::ConstPtr contained = to.containedType();
+ if (m_typeResolver->equals(to.storedType(), contained)
+ || m_typeResolver->isNumeric(to.storedType())
+ || to.storedType()->isReferenceType()
+ || m_typeResolver->equals(from, contained)) {
+ // If:
+ // * the output is not actually wrapped at all, or
+ // * the output is a number (as there are no internals to a number)
+ // * the output is a QObject pointer, or
+ // * we merely wrap the value into a new container,
+ // we can convert by stored type.
+ return convertStored(from, to.storedType(), variable);
+ } else {
+ return convertContained(
+ m_typeResolver->globalType(from).storedIn(m_typeResolver->storedType(from)),
+ to, variable);
+ }
}
- QString conversion(const QQmlJSScope::ConstPtr &from,
- const QQmlJSScope::ConstPtr &to,
- const QString &variable);
+ QString convertStored(const QQmlJSScope::ConstPtr &from,
+ const QQmlJSScope::ConstPtr &to,
+ const QString &variable);
+
+ QString convertContained(const QQmlJSRegisterContent &from,
+ const QQmlJSRegisterContent &to,
+ const QString &variable);
- QString errorReturnValue();
+ void generateReturnError();
void reject(const QString &thing);
QString metaTypeFromType(const QQmlJSScope::ConstPtr &type) const;
QString metaTypeFromName(const QQmlJSScope::ConstPtr &type) const;
+ QString compositeMetaType(const QString &elementName) const;
+ QString compositeListMetaType(const QString &elementName) const;
+
QString contentPointer(const QQmlJSRegisterContent &content, const QString &var);
QString contentType(const QQmlJSRegisterContent &content, const QString &var);
@@ -237,11 +265,14 @@ protected:
void generateEnumLookup(int index);
QString registerVariable(int index) const;
+ QString lookupVariable(int lookupIndex) const;
QString consumedRegisterVariable(int index) const;
QString consumedAccumulatorVariableIn() const;
QString changedRegisterVariable() const;
QQmlJSRegisterContent registerType(int index) const;
+ QQmlJSRegisterContent lookupType(int lookupIndex) const;
+ bool shouldMoveRegister(int index) const;
QString m_body;
CodegenState m_state;
@@ -250,7 +281,18 @@ protected:
private:
void generateExceptionCheck();
- void generateEqualityOperation(int lhs, const QString &function, bool invert);
+
+ void generateEqualityOperation(
+ const QQmlJSRegisterContent &lhsContent, const QString &lhsName,
+ const QString &function, bool invert) {
+ generateEqualityOperation(
+ lhsContent, m_state.accumulatorIn(), lhsName, m_state.accumulatorVariableIn,
+ function, invert);
+ }
+
+ void generateEqualityOperation(
+ const QQmlJSRegisterContent &lhsContent, const QQmlJSRegisterContent &rhsContent,
+ const QString &lhsName, const QString &rhsName, const QString &function, bool invert);
void generateCompareOperation(int lhs, const QString &cppOperator);
void generateArithmeticOperation(int lhs, const QString &cppOperator);
void generateShiftOperation(int lhs, const QString &cppOperator);
@@ -262,10 +304,16 @@ private:
void generateInPlaceOperation(const QString &cppOperator);
void generateMoveOutVar(const QString &outVar);
void generateTypeLookup(int index);
- void generateOutputVariantConversion(const QQmlJSScope::ConstPtr &containedType);
- void generateVariantEqualityComparison(const QQmlJSRegisterContent &nonStorable,
- const QString &registerName, bool invert);
+ void generateVariantEqualityComparison(
+ const QQmlJSRegisterContent &nonStorable, const QString &registerName, bool invert);
+ void generateVariantEqualityComparison(
+ const QQmlJSRegisterContent &storableContent, const QString &typedRegisterName,
+ const QString &varRegisterName, bool invert);
+ void generateArrayInitializer(int argc, int argv);
+ void generateWriteBack(int registerIndex);
void rejectIfNonQObjectOut(const QString &error);
+ void rejectIfBadArray();
+
QString eqIntExpression(int lhsConst);
QString argumentsList(int argc, int argv, QString *outVar);
@@ -275,34 +323,86 @@ private:
bool inlineTranslateMethod(const QString &name, int argc, int argv);
bool inlineMathMethod(const QString &name, int argc, int argv);
bool inlineConsoleMethod(const QString &name, int argc, int argv);
+ bool inlineArrayMethod(const QString &name, int base, int argc, int argv);
+
+ void generate_GetLookupHelper(int index);
+
+ QString resolveValueTypeContentPointer(
+ const QQmlJSScope::ConstPtr &required, const QQmlJSRegisterContent &actual,
+ const QString &variable, const QString &errorMessage);
+ QString resolveQObjectPointer(
+ const QQmlJSScope::ConstPtr &required, const QQmlJSRegisterContent &actual,
+ const QString &variable, const QString &errorMessage);
+ bool generateContentPointerCheck(
+ const QQmlJSScope::ConstPtr &required, const QQmlJSRegisterContent &actual,
+ const QString &variable, const QString &errorMessage);
- QQmlJSScope::ConstPtr mathObject() const
+ QQmlJSRegisterContent original(const QQmlJSRegisterContent &tracked)
{
- using namespace Qt::StringLiterals;
- return m_typeResolver->jsGlobalObject()->property(u"Math"_s).type();
+ const QQmlJSRegisterContent restored = m_typeResolver->original(tracked);
+ return restored.storedIn(m_typeResolver->originalType(tracked.storedType()));
}
- QQmlJSScope::ConstPtr consoleObject() const
+ QQmlJSRegisterContent global(const QQmlJSScope::ConstPtr &contained)
{
- using namespace Qt::StringLiterals;
- return m_typeResolver->jsGlobalObject()->property(u"console"_s).type();
+ return m_typeResolver->globalType(contained).storedIn(contained);
}
- int nextJSLine(uint line) const;
+ QQmlJSRegisterContent builtin(const QQmlJSScope::ConstPtr &contained)
+ {
+ return m_typeResolver->builtinType(contained).storedIn(contained);
+ }
- QStringList m_sourceCodeLines;
+ bool registerIsStoredIn(
+ const QQmlJSRegisterContent &reg, const QQmlJSScope::ConstPtr &type) const
+ {
+ return m_typeResolver->equals(reg.storedType(), type);
+ }
// map from instruction offset to sequential label number
QHash<int, QString> m_labels;
const QV4::Compiler::Context *m_context = nullptr;
- const InstructionAnnotations *m_annotations = nullptr;
- int m_lastLineNumberUsed = -1;
bool m_skipUntilNextLabel = false;
QStringList m_includes;
- QHash<int, QHash<QQmlJSScope::ConstPtr, QString>> m_registerVariables;
+
+ struct RegisterVariablesKey
+ {
+ QString internalName;
+ int registerIndex = -1;
+ int lookupIndex = QQmlJSRegisterContent::InvalidLookupIndex;
+
+ private:
+ friend size_t qHash(const RegisterVariablesKey &key, size_t seed = 0) noexcept
+ {
+ return qHashMulti(seed, key.internalName, key.registerIndex, key.lookupIndex);
+ }
+
+ friend bool operator==(
+ const RegisterVariablesKey &lhs, const RegisterVariablesKey &rhs) noexcept
+ {
+ return lhs.registerIndex == rhs.registerIndex
+ && lhs.lookupIndex == rhs.lookupIndex
+ && lhs.internalName == rhs.internalName;
+ }
+
+ friend bool operator!=(
+ const RegisterVariablesKey &lhs, const RegisterVariablesKey &rhs) noexcept
+ {
+ return !(lhs == rhs);
+ }
+ };
+
+ struct RegisterVariablesValue
+ {
+ QString variableName;
+ QQmlJSScope::ConstPtr storedType;
+ int numTracked = 0;
+ };
+
+ QHash<RegisterVariablesKey, RegisterVariablesValue> m_registerVariables;
};
QT_END_NAMESPACE
diff --git a/src/qmlcompiler/qqmljscompilepass_p.h b/src/qmlcompiler/qqmljscompilepass_p.h
index 139e970750..d396efa4de 100644
--- a/src/qmlcompiler/qqmljscompilepass_p.h
+++ b/src/qmlcompiler/qqmljscompilepass_p.h
@@ -32,6 +32,7 @@ public:
enum RegisterShortcuts {
InvalidRegister = -1,
Accumulator = QV4::CallData::Accumulator,
+ This = QV4::CallData::This,
FirstArgument = QV4::CallData::OffsetCount
};
@@ -41,17 +42,31 @@ public:
{
QQmlJSRegisterContent content;
bool canMove = false;
+ bool affectedBySideEffects = false;
private:
friend bool operator==(const VirtualRegister &a, const VirtualRegister &b)
{
- return a.content == b.content && a.canMove == b.canMove;
+ return a.content == b.content && a.canMove == b.canMove
+ && a.affectedBySideEffects == b.affectedBySideEffects;
}
};
// map from register index to expected type
using VirtualRegisters = QFlatMap<int, VirtualRegister>;
+ struct BasicBlock
+ {
+ QList<int> jumpOrigins;
+ QList<int> readRegisters;
+ int jumpTarget = -1;
+ bool jumpIsUnconditional = false;
+ bool isReturnBlock = false;
+ bool isThrowBlock = false;
+ };
+
+ using BasicBlocks = QFlatMap<int, BasicBlock>;
+
struct InstructionAnnotation
{
// Registers explicit read as part of the instruction.
@@ -67,25 +82,56 @@ public:
};
using InstructionAnnotations = QFlatMap<int, InstructionAnnotation>;
+ struct BlocksAndAnnotations
+ {
+ BasicBlocks basicBlocks;
+ InstructionAnnotations annotations;
+ };
struct Function
{
QQmlJSScopesById addressableScopes;
QList<QQmlJSRegisterContent> argumentTypes;
QList<QQmlJSRegisterContent> registerTypes;
- QQmlJSScope::ConstPtr returnType;
- QQmlJSScope::ConstPtr qmlScope;
+ QQmlJSRegisterContent returnType;
+ QQmlJSRegisterContent qmlScope;
QByteArray code;
const SourceLocationTable *sourceLocations = nullptr;
bool isSignalHandler = false;
bool isQPropertyBinding = false;
bool isProperty = false;
+ bool isFullyTyped = false;
+ };
+
+ struct ObjectOrArrayDefinition
+ {
+ enum {
+ ArrayClassId = -1,
+ ArrayConstruct1ArgId = -2,
+ };
+
+ int instructionOffset = -1;
+ int internalClassId = ArrayClassId;
+ int argc = 0;
+ int argv = -1;
};
struct State
{
VirtualRegisters registers;
-
+ VirtualRegisters lookups;
+
+ /*!
+ \internal
+ \brief The accumulatorIn is the input register of the current instruction.
+
+ It holds a content, a type that content is acctually stored in, and an enclosing type
+ of the stored type called the scope. Note that passes after the original type
+ propagation may change the type of this register to a different type that the original
+ one can be coerced to. Therefore, when analyzing the same instruction in a later pass,
+ the type may differ from what was seen or requested ealier. See \l {readAccumulator()}.
+ The input type may then need to be converted to the expected type.
+ */
const QQmlJSRegisterContent &accumulatorIn() const
{
auto it = registers.find(Accumulator);
@@ -93,6 +139,10 @@ public:
return it.value().content;
};
+ /*!
+ \internal
+ \brief The accumulatorOut is the output register of the current instruction.
+ */
const QQmlJSRegisterContent &accumulatorOut() const
{
Q_ASSERT(m_changedRegisterIndex == Accumulator);
@@ -101,6 +151,10 @@ public:
void setRegister(int registerIndex, QQmlJSRegisterContent content)
{
+ const int lookupIndex = content.resultLookupIndex();
+ if (lookupIndex != QQmlJSRegisterContent::InvalidLookupIndex)
+ lookups[lookupIndex] = { content, false, false };
+
m_changedRegister = std::move(content);
m_changedRegisterIndex = registerIndex;
}
@@ -117,7 +171,11 @@ public:
void addReadRegister(int registerIndex, const QQmlJSRegisterContent &reg)
{
Q_ASSERT(isRename() || reg.isConversion());
- m_readRegisters[registerIndex].content = reg;
+ const VirtualRegister &source = registers[registerIndex];
+ VirtualRegister &target = m_readRegisters[registerIndex];
+ target.content = reg;
+ target.canMove = source.canMove;
+ target.affectedBySideEffects = source.affectedBySideEffects;
}
void addReadAccumulator(const QQmlJSRegisterContent &reg)
@@ -143,6 +201,19 @@ public:
return it != m_readRegisters.end() && it->second.canMove;
}
+ bool isRegisterAffectedBySideEffects(int registerIndex) const
+ {
+ auto it = m_readRegisters.find(registerIndex);
+ return it != m_readRegisters.end() && it->second.affectedBySideEffects;
+ }
+
+ /*!
+ \internal
+ \brief The readAccumulator is the register content expected by the current instruction.
+
+ It may differ from the actual input type of the accumulatorIn register and usage of the
+ value may require a conversion.
+ */
QQmlJSRegisterContent readAccumulator() const
{
return readRegister(Accumulator);
@@ -154,11 +225,35 @@ public:
}
bool hasSideEffects() const { return m_hasSideEffects; }
- void setHasSideEffects(bool hasSideEffects) { m_hasSideEffects = hasSideEffects; }
+
+ void markSideEffects(bool hasSideEffects) { m_hasSideEffects = hasSideEffects; }
+ void applySideEffects(bool hasSideEffects)
+ {
+ if (!hasSideEffects)
+ return;
+
+ for (auto it = registers.begin(), end = registers.end(); it != end; ++it)
+ it.value().affectedBySideEffects = true;
+
+ for (auto it = lookups.begin(), end = lookups.end(); it != end; ++it)
+ it.value().affectedBySideEffects = true;
+ }
+
+ void setHasSideEffects(bool hasSideEffects) {
+ markSideEffects(hasSideEffects);
+ applySideEffects(hasSideEffects);
+ }
bool isRename() const { return m_isRename; }
void setIsRename(bool isRename) { m_isRename = isRename; }
+ int renameSourceRegisterIndex() const
+ {
+ Q_ASSERT(m_isRename);
+ Q_ASSERT(m_readRegisters.size() == 1);
+ return m_readRegisters.begin().key();
+ }
+
private:
VirtualRegisters m_readRegisters;
QQmlJSRegisterContent m_changedRegister;
@@ -168,10 +263,13 @@ public:
};
QQmlJSCompilePass(const QV4::Compiler::JSUnitGenerator *jsUnitGenerator,
- const QQmlJSTypeResolver *typeResolver, QQmlJSLogger *logger)
+ const QQmlJSTypeResolver *typeResolver, QQmlJSLogger *logger,
+ BasicBlocks basicBlocks = {}, InstructionAnnotations annotations = {})
: m_jsUnitGenerator(jsUnitGenerator)
, m_typeResolver(typeResolver)
, m_logger(logger)
+ , m_basicBlocks(basicBlocks)
+ , m_annotations(annotations)
{}
protected:
@@ -180,6 +278,8 @@ protected:
QQmlJSLogger *m_logger = nullptr;
const Function *m_function = nullptr;
+ BasicBlocks m_basicBlocks;
+ InstructionAnnotations m_annotations;
QQmlJS::DiagnosticMessage *m_error = nullptr;
int firstRegisterIndex() const
@@ -218,17 +318,23 @@ protected:
const auto instruction = annotations.find(currentInstructionOffset());
newState.registers = oldState.registers;
+ newState.lookups = oldState.lookups;
// Usually the initial accumulator type is the output of the previous instruction, but ...
if (oldState.changedRegisterIndex() != InvalidRegister) {
+ newState.registers[oldState.changedRegisterIndex()].affectedBySideEffects = false;
newState.registers[oldState.changedRegisterIndex()].content
= oldState.changedRegister();
}
+ // Side effects are applied at the end of an instruction: An instruction with side
+ // effects can still read its registers before the side effects happen.
+ newState.applySideEffects(oldState.hasSideEffects());
+
if (instruction == annotations.constEnd())
return newState;
- newState.setHasSideEffects(instruction->second.hasSideEffects);
+ newState.markSideEffects(instruction->second.hasSideEffects);
newState.setReadRegisters(instruction->second.readRegisters);
newState.setIsRename(instruction->second.isRename);
@@ -277,6 +383,31 @@ protected:
setError(message, currentInstructionOffset());
}
+ 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;
+ }
+
// Stub out all the methods so that passes can choose to only implement part of them.
void generate_Add(int) override {}
void generate_As(int) override {}
@@ -337,9 +468,9 @@ protected:
void generate_GetTemplateObject(int) override {}
void generate_Increment() override {}
void generate_InitializeBlockDeadTemporalZone(int, int) override {}
- void generate_IteratorClose(int) override {}
+ void generate_IteratorClose() override {}
void generate_IteratorNext(int, int) override {}
- void generate_IteratorNextForYieldStar(int, int) override {}
+ void generate_IteratorNextForYieldStar(int, int, int) override {}
void generate_Jump(int) override {}
void generate_JumpFalse(int) override {}
void generate_JumpNoException(int) override {}
diff --git a/src/qmlcompiler/qqmljscompiler.cpp b/src/qmlcompiler/qqmljscompiler.cpp
index 964c01c1a2..d55140f964 100644
--- a/src/qmlcompiler/qqmljscompiler.cpp
+++ b/src/qmlcompiler/qqmljscompiler.cpp
@@ -6,19 +6,24 @@
#include <private/qqmlirbuilder_p.h>
#include <private/qqmljsbasicblocks_p.h>
#include <private/qqmljscodegenerator_p.h>
+#include <private/qqmljscompilerstats_p.h>
#include <private/qqmljsfunctioninitializer_p.h>
#include <private/qqmljsimportvisitor_p.h>
#include <private/qqmljslexer_p.h>
#include <private/qqmljsloadergenerator_p.h>
+#include <private/qqmljsoptimizations_p.h>
#include <private/qqmljsparser_p.h>
#include <private/qqmljsshadowcheck_p.h>
#include <private/qqmljsstoragegeneralizer_p.h>
+#include <private/qqmljsstorageinitializer_p.h>
#include <private/qqmljstypepropagator_p.h>
#include <QtCore/qfile.h>
#include <QtCore/qfileinfo.h>
#include <QtCore/qloggingcategory.h>
+#include <QtQml/private/qqmlsignalnames_p.h>
+
#include <limits>
QT_BEGIN_NAMESPACE
@@ -123,9 +128,7 @@ static bool checkArgumentsObjectUseInSignalHandlers(const QmlIR::Document &doc,
if (binding->type() != QV4::CompiledData::Binding::Type_Script)
continue;
const QString propName = doc.stringAt(binding->propertyNameIndex);
- if (!propName.startsWith(QLatin1String("on"))
- || propName.size() < 3
- || !propName.at(2).isUpper())
+ if (!QQmlSignalNames::isHandlerName(propName))
continue;
auto compiledFunction = doc.jsModule.functions.value(object->runtimeFunctionIndices.at(binding->value.compiledScriptIndex));
if (!compiledFunction)
@@ -363,8 +366,8 @@ bool qCompileQmlFile(QmlIR::Document &irDocument, const QString &inputFileName,
const quint32 saveFlags
= QV4::CompiledData::Unit::StaticData
| QV4::CompiledData::Unit::PendingTypeCompilation;
- QV4::CompiledData::SaveableUnitPointer saveable(irDocument.javaScriptCompilationUnit.data,
- saveFlags);
+ QV4::CompiledData::SaveableUnitPointer saveable(
+ irDocument.javaScriptCompilationUnit->unitData(), saveFlags);
if (!saveFunction(saveable, aotFunctionsByIndex, &error->message))
return false;
}
@@ -373,7 +376,7 @@ bool qCompileQmlFile(QmlIR::Document &irDocument, const QString &inputFileName,
bool qCompileJSFile(const QString &inputFileName, const QString &inputFileUrl, QQmlJSSaveFunction saveFunction, QQmlJSCompileError *error)
{
- QV4::CompiledData::CompilationUnit unit;
+ QQmlRefPointer<QV4::CompiledData::CompilationUnit> unit;
QString sourceCode;
{
@@ -397,7 +400,7 @@ bool qCompileJSFile(const QString &inputFileName, const QString &inputFileUrl, Q
unit = QV4::Compiler::Codegen::compileModule(/*debugMode*/false, url, sourceCode,
QDateTime(), &diagnostics);
error->appendDiagnostics(inputFileName, diagnostics);
- if (!unit.unitData())
+ if (!unit || !unit->unitData())
return false;
} else {
QmlIR::Document irDocument(/*debugMode*/false);
@@ -455,32 +458,14 @@ bool qCompileJSFile(const QString &inputFileName, const QString &inputFileUrl, Q
}
QQmlJSAotFunctionMap empty;
- return saveFunction(QV4::CompiledData::SaveableUnitPointer(unit.data), empty, &error->message);
-}
-
-static const char *wrapCallCode = R"(
-template <typename Binding>
-void wrapCall(const QQmlPrivate::AOTCompiledContext *aotContext, void *dataPtr, void **argumentsPtr, Binding &&binding)
-{
- using return_type = std::invoke_result_t<Binding, const QQmlPrivate::AOTCompiledContext *, void **>;
- if constexpr (std::is_same_v<return_type, void>) {
- Q_UNUSED(dataPtr)
- binding(aotContext, argumentsPtr);
- } else {
- if (dataPtr) {
- new (dataPtr) return_type(binding(aotContext, argumentsPtr));
- } else {
- binding(aotContext, argumentsPtr);
- }
- }
+ return saveFunction(
+ QV4::CompiledData::SaveableUnitPointer(unit->unitData()), empty, &error->message);
}
-)";
static const char *funcHeaderCode = R"(
- [](const QQmlPrivate::AOTCompiledContext *aotContext, void *dataPtr, void **argumentsPtr) {
- wrapCall(aotContext, dataPtr, argumentsPtr, [](const QQmlPrivate::AOTCompiledContext *aotContext, void **argumentsPtr) {
+ [](const QQmlPrivate::AOTCompiledContext *aotContext, void **argv) {
Q_UNUSED(aotContext)
-Q_UNUSED(argumentsPtr)
+Q_UNUSED(argv)
)";
bool qSaveQmlJSUnitAsCpp(const QString &inputFileName, const QString &outputFileName, const QV4::CompiledData::SaveableUnitPointer &unit, const QQmlJSAotFunctionMap &aotFunctions, QString *errorString)
@@ -576,14 +561,13 @@ bool qSaveQmlJSUnitAsCpp(const QString &inputFileName, const QString &outputFile
writeStr(aotFunctions[FileScopeCodeIndex].code.toUtf8().constData());
if (aotFunctions.size() <= 1) {
// FileScopeCodeIndex is always there, but it may be the only one.
- writeStr("extern const QQmlPrivate::TypedFunction aotBuiltFunctions[];\n"
- "extern const QQmlPrivate::TypedFunction aotBuiltFunctions[] = { { 0, QMetaType::fromType<void>(), {}, nullptr } };");
+ writeStr("extern const QQmlPrivate::AOTCompiledFunction aotBuiltFunctions[];\n"
+ "extern const QQmlPrivate::AOTCompiledFunction aotBuiltFunctions[] = { { 0, 0, nullptr, nullptr } };\n");
} else {
- writeStr(wrapCallCode);
- writeStr("extern const QQmlPrivate::TypedFunction aotBuiltFunctions[];\n"
- "extern const QQmlPrivate::TypedFunction aotBuiltFunctions[] = {\n");
+ writeStr("extern const QQmlPrivate::AOTCompiledFunction aotBuiltFunctions[];\n"
+ "extern const QQmlPrivate::AOTCompiledFunction aotBuiltFunctions[] = {\n");
- QString footer = QStringLiteral("});}\n");
+ QString footer = QStringLiteral("}\n");
for (QQmlJSAotFunctionMap::ConstIterator func = aotFunctions.constBegin(),
end = aotFunctions.constEnd();
@@ -592,25 +576,18 @@ bool qSaveQmlJSUnitAsCpp(const QString &inputFileName, const QString &outputFile
if (func.key() == FileScopeCodeIndex)
continue;
- QString function = QString::fromUtf8(funcHeaderCode) + func.value().code + footer;
-
- QString argumentTypes = func.value().argumentTypes.join(
- QStringLiteral(">(), QMetaType::fromType<"));
- if (!argumentTypes.isEmpty()) {
- argumentTypes = QStringLiteral("QMetaType::fromType<")
- + argumentTypes + QStringLiteral(">()");
- }
+ const QString function = QString::fromUtf8(funcHeaderCode) + func.value().code + footer;
- writeStr(QStringLiteral("{ %1, QMetaType::fromType<%2>(), { %3 }, %4 },")
+ writeStr(QStringLiteral("{ %1, %2, [](QV4::ExecutableCompilationUnit *unit, "
+ "QMetaType *argTypes) {\n%3}, %4 },")
.arg(func.key())
- .arg(func.value().returnType)
- .arg(argumentTypes)
- .arg(function)
+ .arg(func->numArguments)
+ .arg(func->signature, function)
.toUtf8().constData());
}
// Conclude the list with a nullptr
- writeStr("{ 0, QMetaType::fromType<void>(), {}, nullptr }");
+ writeStr("{ 0, 0, nullptr, nullptr }");
writeStr("};\n");
}
@@ -650,7 +627,6 @@ void QQmlJSAotCompiler::setDocument(
m_logger->setFileName(resourcePathInfo.fileName());
m_logger->setCode(irDocument->code);
m_unitGenerator = &irDocument->jsGenerator;
- m_entireSourceCodeLines = irDocument->code.split(u'\n');
QQmlJSScope::Ptr target = QQmlJSScope::create();
QQmlJSImportVisitor visitor(target, m_importer, m_logger,
resourcePathInfo.canonicalPath() + u'/',
@@ -705,7 +681,8 @@ std::variant<QQmlJSAotFunction, QQmlJS::DiagnosticMessage> QQmlJSAotCompiler::co
const QString name = m_document->stringAt(irBinding.propertyNameIndex);
QQmlJSCompilePass::Function function = initializer.run(
context, name, astNode, irBinding, &error);
- const QQmlJSAotFunction aotFunction = doCompile(context, &function, &error);
+ const QQmlJSAotFunction aotFunction = doCompileAndRecordAotStats(
+ context, &function, &error, name, astNode->firstSourceLocation());
if (error.isValid()) {
// If it's a signal and the function just returns a closure, it's harmless.
@@ -729,7 +706,8 @@ std::variant<QQmlJSAotFunction, QQmlJS::DiagnosticMessage> QQmlJSAotCompiler::co
&m_typeResolver, m_currentObject->location, m_currentScope->location);
QQmlJS::DiagnosticMessage error;
QQmlJSCompilePass::Function function = initializer.run(context, name, astNode, &error);
- const QQmlJSAotFunction aotFunction = doCompile(context, &function, &error);
+ const QQmlJSAotFunction aotFunction = doCompileAndRecordAotStats(
+ context, &function, &error, name, astNode->firstSourceLocation());
if (error.isValid())
return diagnose(error.message, QtWarningMsg, error.loc);
@@ -764,7 +742,6 @@ QQmlJSAotFunction QQmlJSAotCompiler::globalCode() const
return global;
}
-
QQmlJSAotFunction QQmlJSAotCompiler::doCompile(
const QV4::Compiler::Context *context, QQmlJSCompilePass::Function *function,
QQmlJS::DiagnosticMessage *error)
@@ -775,31 +752,67 @@ QQmlJSAotFunction QQmlJSAotCompiler::doCompile(
return QQmlJSAotFunction();
};
- QQmlJSTypePropagator propagator(m_unitGenerator, &m_typeResolver, m_logger);
- auto typePropagationResult = propagator.run(function, error);
if (error->isValid())
return compileError();
- QQmlJSBasicBlocks basicBlocks(m_unitGenerator, &m_typeResolver, m_logger);
- typePropagationResult = basicBlocks.run(function, typePropagationResult);
+ bool basicBlocksValidationFailed = false;
+ QQmlJSBasicBlocks basicBlocks(context, m_unitGenerator, &m_typeResolver, m_logger);
+ auto passResult = basicBlocks.run(function, m_flags, basicBlocksValidationFailed);
+ auto &[blocks, annotations] = passResult;
+
+ QQmlJSTypePropagator propagator(m_unitGenerator, &m_typeResolver, m_logger, blocks, annotations);
+ passResult = propagator.run(function, error);
+ if (error->isValid())
+ return compileError();
+
+ QQmlJSShadowCheck shadowCheck(m_unitGenerator, &m_typeResolver, m_logger, blocks, annotations);
+ passResult = shadowCheck.run(function, error);
+ if (error->isValid())
+ return compileError();
- QQmlJSShadowCheck shadowCheck(m_unitGenerator, &m_typeResolver, m_logger);
- shadowCheck.run(&typePropagationResult, function, error);
+ QQmlJSOptimizations optimizer(m_unitGenerator, &m_typeResolver, m_logger, blocks, annotations,
+ basicBlocks.objectAndArrayDefinitions());
+ passResult = optimizer.run(function, error);
if (error->isValid())
return compileError();
+ QQmlJSStorageInitializer initializer(
+ m_unitGenerator, &m_typeResolver, m_logger, blocks, annotations);
+ passResult = initializer.run(function, error);
+
// Generalize all arguments, registers, and the return type.
QQmlJSStorageGeneralizer generalizer(
- m_unitGenerator, &m_typeResolver, m_logger);
- typePropagationResult = generalizer.run(typePropagationResult, function, error);
+ m_unitGenerator, &m_typeResolver, m_logger, blocks, annotations);
+ passResult = generalizer.run(function, error);
if (error->isValid())
return compileError();
- QQmlJSCodeGenerator codegen(
- context, m_unitGenerator, &m_typeResolver, m_logger,
- m_entireSourceCodeLines);
- QQmlJSAotFunction result = codegen.run(function, &typePropagationResult, error);
+ QQmlJSCodeGenerator codegen(context, m_unitGenerator, &m_typeResolver, m_logger, blocks, annotations);
+ QQmlJSAotFunction result = codegen.run(function, error, basicBlocksValidationFailed);
return error->isValid() ? compileError() : result;
}
+QQmlJSAotFunction QQmlJSAotCompiler::doCompileAndRecordAotStats(
+ const QV4::Compiler::Context *context, QQmlJSCompilePass::Function *function,
+ QQmlJS::DiagnosticMessage *error, const QString &name, QQmlJS::SourceLocation location)
+{
+ auto t1 = std::chrono::high_resolution_clock::now();
+ QQmlJSAotFunction result = doCompile(context, function, error);
+ auto t2 = std::chrono::high_resolution_clock::now();
+
+ if (QQmlJS::QQmlJSAotCompilerStats::recordAotStats()) {
+ QQmlJS::AotStatsEntry entry;
+ entry.codegenDuration = std::chrono::duration_cast<std::chrono::microseconds>(t2 - t1);
+ entry.functionName = name;
+ entry.errorMessage = error->message;
+ entry.line = location.startLine;
+ entry.column = location.startColumn;
+ entry.codegenSuccessful = !error->isValid();
+ QQmlJS::QQmlJSAotCompilerStats::addEntry(
+ function->qmlScope.containedType()->filePath(), entry);
+ }
+
+ return result;
+}
+
QT_END_NAMESPACE
diff --git a/src/qmlcompiler/qqmljscompiler_p.h b/src/qmlcompiler/qqmljscompiler_p.h
index 3483226a09..94cf71b884 100644
--- a/src/qmlcompiler/qqmljscompiler_p.h
+++ b/src/qmlcompiler/qqmljscompiler_p.h
@@ -14,7 +14,7 @@
//
// We mean it.
-#include <private/qtqmlcompilerexports_p.h>
+#include <qtqmlcompilerexports.h>
#include <QtCore/qstring.h>
#include <QtCore/qlist.h>
@@ -22,6 +22,7 @@
#include <private/qqmlirbuilder_p.h>
#include <private/qqmljscompilepass_p.h>
+#include <private/qqmljscompilerstats_p.h>
#include <private/qqmljsdiagnosticmessage_p.h>
#include <private/qqmljsimporter_p.h>
#include <private/qqmljslogger_p.h>
@@ -32,9 +33,9 @@
QT_BEGIN_NAMESPACE
-Q_QMLCOMPILER_PRIVATE_EXPORT Q_DECLARE_LOGGING_CATEGORY(lcAotCompiler);
+QT_DECLARE_EXPORTED_QT_LOGGING_CATEGORY(lcAotCompiler, Q_QMLCOMPILER_EXPORT);
-struct Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSCompileError
+struct Q_QMLCOMPILER_EXPORT QQmlJSCompileError
{
QString message;
void print();
@@ -45,17 +46,23 @@ struct Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSCompileError
const QQmlJS::DiagnosticMessage &diagnostic);
};
-struct Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSAotFunction
+struct Q_QMLCOMPILER_EXPORT QQmlJSAotFunction
{
QStringList includes;
- QStringList argumentTypes;
QString code;
- QString returnType;
+ QString signature;
+ int numArguments = 0;
};
-class Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSAotCompiler
+class Q_QMLCOMPILER_EXPORT QQmlJSAotCompiler
{
public:
+ enum Flag {
+ NoFlags = 0x0,
+ ValidateBasicBlocks = 0x1,
+ };
+ Q_DECLARE_FLAGS(Flags, Flag)
+
QQmlJSAotCompiler(QQmlJSImporter *importer, const QString &resourcePath,
const QStringList &qmldirFiles, QQmlJSLogger *logger);
@@ -71,12 +78,13 @@ public:
virtual QQmlJSAotFunction globalCode() const;
+ Flags m_flags;
+
protected:
virtual QQmlJS::DiagnosticMessage diagnose(
const QString &message, QtMsgType type, const QQmlJS::SourceLocation &location) const;
QQmlJSTypeResolver m_typeResolver;
- QStringList m_entireSourceCodeLines;
const QString m_resourcePath;
const QStringList m_qmldirFiles;
@@ -90,36 +98,42 @@ protected:
QQmlJSLogger *m_logger = nullptr;
private:
- QQmlJSAotFunction doCompile(
- const QV4::Compiler::Context *context, QQmlJSCompilePass::Function *function,
- QQmlJS::DiagnosticMessage *error);
+ QQmlJSAotFunction doCompile(const QV4::Compiler::Context *context,
+ QQmlJSCompilePass::Function *function,
+ QQmlJS::DiagnosticMessage *error);
+ QQmlJSAotFunction doCompileAndRecordAotStats(const QV4::Compiler::Context *context,
+ QQmlJSCompilePass::Function *function,
+ QQmlJS::DiagnosticMessage *error,
+ const QString &name,
+ QQmlJS::SourceLocation location);
};
+Q_DECLARE_OPERATORS_FOR_FLAGS(QQmlJSAotCompiler::Flags);
using QQmlJSAotFunctionMap = QMap<int, QQmlJSAotFunction>;
using QQmlJSSaveFunction
= std::function<bool(const QV4::CompiledData::SaveableUnitPointer &,
const QQmlJSAotFunctionMap &, QString *)>;
-bool Q_QMLCOMPILER_PRIVATE_EXPORT qCompileQmlFile(const QString &inputFileName,
+bool Q_QMLCOMPILER_EXPORT qCompileQmlFile(const QString &inputFileName,
QQmlJSSaveFunction saveFunction,
QQmlJSAotCompiler *aotCompiler, QQmlJSCompileError *error,
bool storeSourceLocation = false,
QV4::Compiler::CodegenWarningInterface *interface =
QV4::Compiler::defaultCodegenWarningInterface(),
const QString *fileContents = nullptr);
-bool Q_QMLCOMPILER_PRIVATE_EXPORT qCompileQmlFile(QmlIR::Document &irDocument, const QString &inputFileName,
+bool Q_QMLCOMPILER_EXPORT qCompileQmlFile(QmlIR::Document &irDocument, const QString &inputFileName,
QQmlJSSaveFunction saveFunction,
QQmlJSAotCompiler *aotCompiler, QQmlJSCompileError *error,
bool storeSourceLocation = false,
QV4::Compiler::CodegenWarningInterface *interface =
QV4::Compiler::defaultCodegenWarningInterface(),
const QString *fileContents = nullptr);
-bool Q_QMLCOMPILER_PRIVATE_EXPORT qCompileJSFile(const QString &inputFileName, const QString &inputFileUrl,
+bool Q_QMLCOMPILER_EXPORT qCompileJSFile(const QString &inputFileName, const QString &inputFileUrl,
QQmlJSSaveFunction saveFunction,
QQmlJSCompileError *error);
-bool Q_QMLCOMPILER_PRIVATE_EXPORT qSaveQmlJSUnitAsCpp(const QString &inputFileName,
+bool Q_QMLCOMPILER_EXPORT qSaveQmlJSUnitAsCpp(const QString &inputFileName,
const QString &outputFileName,
const QV4::CompiledData::SaveableUnitPointer &unit,
const QQmlJSAotFunctionMap &aotFunctions,
diff --git a/src/qmlcompiler/qqmljscompilerstats.cpp b/src/qmlcompiler/qqmljscompilerstats.cpp
new file mode 100644
index 0000000000..7cef0f39c3
--- /dev/null
+++ b/src/qmlcompiler/qqmljscompilerstats.cpp
@@ -0,0 +1,188 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "qqmljscompilerstats_p.h"
+
+#include <QFile>
+#include <QJsonArray>
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QTextStream>
+
+QT_BEGIN_NAMESPACE
+
+namespace QQmlJS {
+
+using namespace Qt::StringLiterals;
+
+std::unique_ptr<AotStats> QQmlJSAotCompilerStats::s_instance = std::make_unique<AotStats>();
+QString QQmlJSAotCompilerStats::s_moduleId;
+bool QQmlJSAotCompilerStats::s_recordAotStats = false;
+
+bool QQmlJS::AotStatsEntry::operator<(const AotStatsEntry &other) const
+{
+ if (line == other.line)
+ return column < other.column;
+ return line < other.line;
+}
+
+void AotStats::insert(const AotStats &other)
+{
+ for (const auto &[moduleUri, moduleStats] : other.m_entries.asKeyValueRange()) {
+ m_entries[moduleUri].insert(moduleStats);
+ }
+}
+
+std::optional<QList<QString>> extractAotstatsFilesList(const QString &aotstatsListPath)
+{
+ QFile aotstatsListFile(aotstatsListPath);
+ if (!aotstatsListFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
+ qDebug().noquote() << u"Could not open \"%1\" for reading"_s.arg(aotstatsListFile.fileName());
+ return std::nullopt;
+ }
+
+ QStringList aotstatsFiles;
+ QTextStream stream(&aotstatsListFile);
+ while (!stream.atEnd())
+ aotstatsFiles.append(stream.readLine());
+
+ return aotstatsFiles;
+}
+
+std::optional<AotStats> AotStats::parseAotstatsFile(const QString &aotstatsPath)
+{
+ QFile file(aotstatsPath);
+ if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
+ qDebug().noquote() << u"Could not open \"%1\""_s.arg(aotstatsPath);
+ return std::nullopt;
+ }
+
+ return AotStats::fromJsonDocument(QJsonDocument::fromJson(file.readAll()));
+}
+
+std::optional<AotStats> AotStats::aggregateAotstatsList(const QString &aotstatsListPath)
+{
+ const auto aotstatsFiles = extractAotstatsFilesList(aotstatsListPath);
+ if (!aotstatsFiles.has_value())
+ return std::nullopt;
+
+ AotStats aggregated;
+ if (aotstatsFiles->empty())
+ return aggregated;
+
+ for (const auto &aotstatsFile : aotstatsFiles.value()) {
+ auto parsed = parseAotstatsFile(aotstatsFile);
+ if (!parsed.has_value())
+ return std::nullopt;
+ aggregated.insert(parsed.value());
+ }
+
+ return aggregated;
+}
+
+AotStats AotStats::fromJsonDocument(const QJsonDocument &document)
+{
+ QJsonArray modulesArray = document.array();
+
+ QQmlJS::AotStats result;
+ for (const auto &modulesArrayEntry : modulesArray) {
+ const auto &moduleObject = modulesArrayEntry.toObject();
+ QString moduleId = moduleObject[u"moduleId"_s].toString();
+ const QJsonArray &filesArray = moduleObject[u"moduleFiles"_s].toArray();
+
+ QHash<QString, QList<AotStatsEntry>> files;
+ for (const auto &filesArrayEntry : filesArray) {
+ const QJsonObject &fileObject = filesArrayEntry.toObject();
+ QString filepath = fileObject[u"filepath"_s].toString();
+ const QJsonArray &statsArray = fileObject[u"entries"_s].toArray();
+
+ QList<AotStatsEntry> stats;
+ for (const auto &statsArrayEntry : statsArray) {
+ const auto &statsObject = statsArrayEntry.toObject();
+ QQmlJS::AotStatsEntry stat;
+ auto micros = statsObject[u"durationMicroseconds"_s].toInteger();
+ stat.codegenDuration = std::chrono::microseconds(micros);
+ stat.functionName = statsObject[u"functionName"_s].toString();
+ stat.errorMessage = statsObject[u"errorMessage"_s].toString();
+ stat.line = statsObject[u"line"_s].toInt();
+ stat.column = statsObject[u"column"_s].toInt();
+ stat.codegenSuccessful = statsObject[u"codegenSuccessfull"_s].toBool();
+ stats.append(std::move(stat));
+ }
+
+ std::sort(stats.begin(), stats.end());
+ files[filepath] = std::move(stats);
+ }
+
+ result.m_entries[moduleId] = std::move(files);
+ }
+
+ return result;
+}
+
+QJsonDocument AotStats::toJsonDocument() const
+{
+ QJsonArray modulesArray;
+ for (auto it1 = m_entries.begin(); it1 != m_entries.end(); ++it1) {
+ const QString moduleId = it1.key();
+ const QHash<QString, QList<AotStatsEntry>> &files = it1.value();
+
+ QJsonArray filesArray;
+ for (auto it2 = files.begin(); it2 != files.end(); ++it2) {
+ const QString &filename = it2.key();
+ const QList<AotStatsEntry> &stats = it2.value();
+
+ QJsonArray statsArray;
+ for (const auto &stat : stats) {
+ QJsonObject statObject;
+ auto micros = static_cast<qint64>(stat.codegenDuration.count());
+ statObject.insert(u"durationMicroseconds", micros);
+ statObject.insert(u"functionName", stat.functionName);
+ statObject.insert(u"errorMessage", stat.errorMessage);
+ statObject.insert(u"line", stat.line);
+ statObject.insert(u"column", stat.column);
+ statObject.insert(u"codegenSuccessfull", stat.codegenSuccessful);
+ statsArray.append(statObject);
+ }
+
+ QJsonObject o;
+ o.insert(u"filepath"_s, filename);
+ o.insert(u"entries"_s, statsArray);
+ filesArray.append(o);
+ }
+
+ QJsonObject o;
+ o.insert(u"moduleId"_s, moduleId);
+ o.insert(u"moduleFiles"_s, filesArray);
+ modulesArray.append(o);
+ }
+
+ return QJsonDocument(modulesArray);
+}
+
+void AotStats::addEntry(
+ const QString &moduleId, const QString &filepath, const AotStatsEntry &entry)
+{
+ m_entries[moduleId][filepath].append(entry);
+}
+
+bool AotStats::saveToDisk(const QString &filepath) const
+{
+ QFile file(filepath);
+ if (!file.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
+ qDebug().noquote() << u"Could not open \"%1\""_s.arg(filepath);
+ return false;
+ }
+
+ file.write(this->toJsonDocument().toJson(QJsonDocument::Indented));
+ return true;
+}
+
+void QQmlJSAotCompilerStats::addEntry(const QString &filepath, const QQmlJS::AotStatsEntry &entry)
+{
+ QQmlJSAotCompilerStats::instance()->addEntry(s_moduleId, filepath, entry);
+}
+
+} // namespace QQmlJS
+
+QT_END_NAMESPACE
diff --git a/src/qmlcompiler/qqmljscompilerstats_p.h b/src/qmlcompiler/qqmljscompilerstats_p.h
new file mode 100644
index 0000000000..53ebb9f777
--- /dev/null
+++ b/src/qmlcompiler/qqmljscompilerstats_p.h
@@ -0,0 +1,92 @@
+// 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 QQMLJSCOMPILERSTATS_P_H
+#define QQMLJSCOMPILERSTATS_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#include <qtqmlcompilerexports.h>
+
+#include <QHash>
+#include <QJsonDocument>
+
+#include <private/qqmljsdiagnosticmessage_p.h>
+#include <private/qqmljssourcelocation_p.h>
+
+#include <memory>
+
+QT_BEGIN_NAMESPACE
+
+namespace QQmlJS {
+
+struct Q_QMLCOMPILER_EXPORT AotStatsEntry
+{
+ std::chrono::microseconds codegenDuration;
+ QString functionName;
+ QString errorMessage;
+ int line = 0;
+ int column = 0;
+ bool codegenSuccessful = true;
+
+ bool operator<(const AotStatsEntry &) const;
+};
+
+class Q_QMLCOMPILER_EXPORT AotStats
+{
+ friend class QQmlJSAotCompilerStats;
+
+public:
+ const QHash<QString, QHash<QString, QList<AotStatsEntry>>> &entries() const
+ {
+ return m_entries;
+ }
+
+ void addEntry(const QString &moduleId, const QString &filepath, const AotStatsEntry &entry);
+ void insert(const AotStats &other);
+
+ bool saveToDisk(const QString &filepath) const;
+
+ static std::optional<AotStats> parseAotstatsFile(const QString &aotstatsPath);
+ static std::optional<AotStats> aggregateAotstatsList(const QString &aotstatsListPath);
+
+ static AotStats fromJsonDocument(const QJsonDocument &);
+ QJsonDocument toJsonDocument() const;
+
+private:
+ // module Id -> filename -> stats m_entries
+ QHash<QString, QHash<QString, QList<AotStatsEntry>>> m_entries;
+};
+
+class Q_QMLCOMPILER_EXPORT QQmlJSAotCompilerStats
+{
+public:
+ static AotStats *instance() { return s_instance.get(); }
+
+ static bool recordAotStats() { return s_recordAotStats; }
+ static void setRecordAotStats(bool recordAotStats) { s_recordAotStats = recordAotStats; }
+
+ static QString moduleId() { return s_moduleId; }
+ static void setModuleId(const QString &moduleId) { s_moduleId = moduleId; }
+
+ static void addEntry(const QString &filepath, const QQmlJS::AotStatsEntry &entry);
+
+private:
+ static std::unique_ptr<AotStats> s_instance;
+ static QString s_moduleId;
+ static bool s_recordAotStats;
+};
+
+} // namespace QQmlJS
+
+QT_END_NAMESPACE
+
+#endif // QQMLJSCOMPILERSTATS_P_H
diff --git a/src/qmlcompiler/qqmljscompilerstatsreporter.cpp b/src/qmlcompiler/qqmljscompilerstatsreporter.cpp
new file mode 100644
index 0000000000..1cb17f71fe
--- /dev/null
+++ b/src/qmlcompiler/qqmljscompilerstatsreporter.cpp
@@ -0,0 +1,118 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "qqmljscompilerstatsreporter_p.h"
+
+#include <QFileInfo>
+
+QT_BEGIN_NAMESPACE
+
+namespace QQmlJS {
+
+using namespace Qt::StringLiterals;
+
+AotStatsReporter::AotStatsReporter(const AotStats &aotstats) : m_aotstats(aotstats)
+{
+ for (const auto &[moduleUri, fileEntries] : aotstats.entries().asKeyValueRange()) {
+ for (const auto &[filepath, statsEntries] : fileEntries.asKeyValueRange()) {
+ for (const auto &entry : statsEntries) {
+ m_fileCounters[moduleUri][filepath].codegens += 1;
+ if (entry.codegenSuccessful) {
+ m_fileCounters[moduleUri][filepath].successes += 1;
+ m_successDurations.append(entry.codegenDuration);
+ }
+ }
+ m_moduleCounters[moduleUri].codegens += m_fileCounters[moduleUri][filepath].codegens;
+ m_moduleCounters[moduleUri].successes += m_fileCounters[moduleUri][filepath].successes;
+ }
+ m_totalCounters.codegens += m_moduleCounters[moduleUri].codegens;
+ m_totalCounters.successes += m_moduleCounters[moduleUri].successes;
+ }
+}
+
+void AotStatsReporter::formatDetailedStats(QTextStream &s) const
+{
+ s << "############ AOT COMPILATION STATS ############\n";
+ for (const auto &[moduleUri, fileStats] : m_aotstats.entries().asKeyValueRange()) {
+ s << u"Module %1:\n"_s.arg(moduleUri);
+ if (fileStats.empty()) {
+ s << "No attempts at compiling a binding or function\n";
+ continue;
+ }
+
+ for (const auto &[filename, entries] : fileStats.asKeyValueRange()) {
+ s << u"--File %1\n"_s.arg(filename);
+ if (entries.empty()) {
+ s << " No attempts at compiling a binding or function\n";
+ continue;
+ }
+
+ int successes = m_fileCounters[moduleUri][filename].successes;
+ s << " " << formatSuccessRate(entries.size(), successes) << "\n";
+
+ for (const auto &stat : entries) {
+ s << u" %1: [%2:%3:%4]\n"_s.arg(stat.functionName)
+ .arg(QFileInfo(filename).fileName())
+ .arg(stat.line)
+ .arg(stat.column);
+ s << u" result: "_s << (stat.codegenSuccessful
+ ? u"Success\n"_s
+ : u"Error: "_s + stat.errorMessage + u'\n');
+ s << u" duration: %1us\n"_s.arg(stat.codegenDuration.count());
+ }
+ s << "\n";
+ }
+ }
+}
+
+void AotStatsReporter::formatSummary(QTextStream &s) const
+{
+ s << "############ AOT COMPILATION STATS SUMMARY ############\n";
+ if (m_totalCounters.codegens == 0) {
+ s << "No attempted compilations to Cpp for bindings or functions.\n";
+ return;
+ }
+
+ for (const auto &moduleUri : m_aotstats.entries().keys()) {
+ const auto &counters = m_moduleCounters[moduleUri];
+ s << u"Module %1: "_s.arg(moduleUri)
+ << formatSuccessRate(counters.codegens, counters.successes) << "\n";
+ }
+
+ s << "Total results: " << formatSuccessRate(m_totalCounters.codegens, m_totalCounters.successes);
+ s << "\n";
+
+ if (m_totalCounters.successes != 0) {
+ auto totalDuration = std::accumulate(m_successDurations.cbegin(), m_successDurations.cend(),
+ std::chrono::microseconds(0));
+ const auto averageDuration = totalDuration.count() / m_totalCounters.successes;
+ s << u"Successful codegens took an average of %1us\n"_s.arg(averageDuration);
+ }
+}
+
+QString AotStatsReporter::format() const
+{
+ QString output;
+ QTextStream s(&output);
+
+ formatDetailedStats(s);
+ formatSummary(s);
+
+ return output;
+}
+
+QString AotStatsReporter::formatSuccessRate(int codegens, int successes) const
+{
+ if (codegens == 0)
+ return u"No attempted compilations"_s;
+
+ return u"%1 of %2 (%3%4) bindings or functions compiled to Cpp successfully"_s
+ .arg(successes)
+ .arg(codegens)
+ .arg(double(successes) / codegens * 100, 0, 'g', 4)
+ .arg(u"%"_s);
+}
+
+} // namespace QQmlJS
+
+QT_END_NAMESPACE
diff --git a/src/qmlcompiler/qqmljscompilerstatsreporter_p.h b/src/qmlcompiler/qqmljscompilerstatsreporter_p.h
new file mode 100644
index 0000000000..9acf4adac8
--- /dev/null
+++ b/src/qmlcompiler/qqmljscompilerstatsreporter_p.h
@@ -0,0 +1,57 @@
+// 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 QQMLJSCOMPILERSTATSREPORTER_P_H
+#define QQMLJSCOMPILERSTATSREPORTER_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#include <QTextStream>
+
+#include <qtqmlcompilerexports.h>
+
+#include <private/qqmljscompilerstats_p.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace QQmlJS {
+
+class Q_QMLCOMPILER_EXPORT AotStatsReporter
+{
+public:
+ AotStatsReporter(const QQmlJS::AotStats &);
+
+ QString format() const;
+
+private:
+ void formatDetailedStats(QTextStream &) const;
+ void formatSummary(QTextStream &) const;
+ QString formatSuccessRate(int codegens, int successes) const;
+
+ const AotStats &m_aotstats;
+
+ struct Counters
+ {
+ int successes = 0;
+ int codegens = 0;
+ };
+
+ Counters m_totalCounters;
+ QHash<QString, Counters> m_moduleCounters;
+ QHash<QString, QHash<QString, Counters>> m_fileCounters;
+ QList<std::chrono::microseconds> m_successDurations;
+};
+
+} // namespace QQmlJS
+
+QT_END_NAMESPACE
+
+#endif // QQMLJSCOMPILERSTATSREPORTER_P_H
diff --git a/src/qmlcompiler/qqmljscontextualtypes_p.h b/src/qmlcompiler/qqmljscontextualtypes_p.h
new file mode 100644
index 0000000000..cccd7fd1b0
--- /dev/null
+++ b/src/qmlcompiler/qqmljscontextualtypes_p.h
@@ -0,0 +1,113 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef QQMLJSCONTEXTUALTYPES_P_H
+#define QQMLJSCONTEXTUALTYPES_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#include <QtCore/qstring.h>
+#include <QtCore/qhash.h>
+#include <private/qqmljsscope_p.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace QQmlJS {
+/*! \internal
+ * Maps type names to types and the compile context of the types. The context can be
+ * INTERNAL (for c++ and synthetic jsrootgen types) or QML (for qml types).
+ */
+struct ContextualTypes
+{
+ enum CompileContext { INTERNAL, QML };
+
+ ContextualTypes(
+ CompileContext context,
+ const QHash<QString, ImportedScope<QQmlJSScope::ConstPtr>> types,
+ const QQmlJSScope::ConstPtr &arrayType)
+ : m_types(types)
+ , m_context(context)
+ , m_arrayType(arrayType)
+ {}
+
+ CompileContext context() const { return m_context; }
+ QQmlJSScope::ConstPtr arrayType() const { return m_arrayType; }
+
+ bool hasType(const QString &name) const { return m_types.contains(name); }
+
+ ImportedScope<QQmlJSScope::ConstPtr> type(const QString &name) const { return m_types[name]; }
+ QString name(const QQmlJSScope::ConstPtr &type) const { return m_names[type]; }
+
+ void setType(const QString &name, const ImportedScope<QQmlJSScope::ConstPtr> &type)
+ {
+ if (!name.startsWith(u'$'))
+ m_names.insert(type.scope, name);
+ m_types.insert(name, type);
+ }
+ void clearType(const QString &name)
+ {
+ auto &scope = m_types[name].scope;
+ auto it = m_names.constFind(scope);
+ while (it != m_names.constEnd() && it.key() == scope)
+ it = m_names.erase(it);
+ scope = QQmlJSScope::ConstPtr();
+ }
+
+ bool isNullType(const QString &name) const
+ {
+ const auto it = m_types.constFind(name);
+ return it != m_types.constEnd() && it->scope.isNull();
+ }
+
+ void addTypes(ContextualTypes &&types)
+ {
+ Q_ASSERT(types.m_context == m_context);
+ insertNames(types);
+ m_types.insert(std::move(types.m_types));
+ }
+
+ void addTypes(const ContextualTypes &types)
+ {
+ Q_ASSERT(types.m_context == m_context);
+ insertNames(types);
+ m_types.insert(types.m_types);
+ }
+
+ const QHash<QString, ImportedScope<QQmlJSScope::ConstPtr>> &types() const { return m_types; }
+
+ void clearTypes()
+ {
+ m_names.clear();
+ m_types.clear();
+ }
+
+private:
+ void insertNames(const ContextualTypes &types) {
+ for (auto it = types.m_types.constBegin(), end = types.m_types.constEnd();
+ it != end; ++it) {
+ const QString &name = it.key();
+ if (!name.startsWith(u'$'))
+ m_names.insert(it->scope, name);
+ }
+ }
+
+ QHash<QString, ImportedScope<QQmlJSScope::ConstPtr>> m_types;
+ QMultiHash<QQmlJSScope::ConstPtr, QString> m_names;
+ CompileContext m_context;
+
+ // For resolving sequence types
+ QQmlJSScope::ConstPtr m_arrayType;
+};
+}
+
+QT_END_NAMESPACE
+
+#endif // QQMLJSCONTEXTUALTYPES_P_H
diff --git a/src/qmlcompiler/qqmljsfunctioninitializer.cpp b/src/qmlcompiler/qqmljsfunctioninitializer.cpp
index 997771489b..a767da68b6 100644
--- a/src/qmlcompiler/qqmljsfunctioninitializer.cpp
+++ b/src/qmlcompiler/qqmljsfunctioninitializer.cpp
@@ -8,6 +8,8 @@
#include <QtCore/qloggingcategory.h>
#include <QtCore/qfileinfo.h>
+#include <QtQml/private/qqmlsignalnames_p.h>
+
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
@@ -59,6 +61,7 @@ void QQmlJSFunctionInitializer::populateSignature(
error->type = QtWarningMsg;
error->loc = ast->firstSourceLocation();
error->message = message;
+ function->isFullyTyped = false;
};
if (!m_typeResolver->canCallJSFunctions()) {
@@ -71,6 +74,11 @@ void QQmlJSFunctionInitializer::populateSignature(
if (ast->formals)
arguments = ast->formals->formals();
+ // If the function has no arguments and no return type annotation we assume it's untyped.
+ // You can annotate it to return void to make it typed.
+ // Otherwise we first assume it's typed and reset the flag if we detect a problem.
+ function->isFullyTyped = !arguments.isEmpty() || ast->typeAnnotation;
+
if (function->argumentTypes.isEmpty()) {
for (const QQmlJS::AST::BoundName &argument : std::as_const(arguments)) {
if (argument.typeAnnotation) {
@@ -108,10 +116,11 @@ void QQmlJSFunctionInitializer::populateSignature(
}
}
- if (!function->returnType) {
+ if (!function->returnType.isValid()) {
if (ast->typeAnnotation) {
- function->returnType = m_typeResolver->typeFromAST(ast->typeAnnotation->type);
- if (!function->returnType)
+ function->returnType = m_typeResolver->globalType(
+ m_typeResolver->typeFromAST(ast->typeAnnotation->type));
+ if (!function->returnType.isValid())
signatureError(u"Cannot resolve return type %1"_s.arg(
QmlIR::IRBuilder::asString(ast->typeAnnotation->type->typeId)));
}
@@ -151,7 +160,7 @@ QQmlJSCompilePass::Function QQmlJSFunctionInitializer::run(
bindingLocation.startColumn = irBinding.location.column();
QQmlJSCompilePass::Function function;
- function.qmlScope = m_scopeType;
+ function.qmlScope = m_typeResolver->globalType(m_scopeType);
if (irBinding.type() != QmlIR::Binding::Type_Script) {
diagnose(u"Binding is not a script binding, but %1."_s.arg(
@@ -160,44 +169,45 @@ QQmlJSCompilePass::Function QQmlJSFunctionInitializer::run(
}
function.isProperty = m_objectType->hasProperty(propertyName);
- if (!function.isProperty && QmlIR::IRBuilder::isSignalPropertyName(propertyName)) {
- const QString signalName = QmlIR::IRBuilder::signalNameFromSignalPropertyName(propertyName);
-
- if (signalName.endsWith(u"Changed"_s)
- && m_objectType->hasProperty(signalName.chopped(strlen("Changed")))) {
- function.isSignalHandler = true;
- } else {
- const auto methods = m_objectType->methods(signalName);
- for (const auto &method : methods) {
- if (method.isCloned())
- continue;
- if (method.methodType() == QQmlJSMetaMethod::Signal) {
- function.isSignalHandler = true;
- const auto arguments = method.parameters();
- for (qsizetype i = 0, end = arguments.size(); i < end; ++i) {
- const auto &type = arguments[i].type();
- if (type.isNull()) {
- diagnose(u"Cannot resolve the argument type %1."_s.arg(
- arguments[i].typeName()),
- QtDebugMsg, bindingLocation, error);
- function.argumentTypes.append(
+ if (!function.isProperty) {
+ if (QQmlSignalNames::isHandlerName(propertyName)) {
+ if (auto actualPropertyName =
+ QQmlSignalNames::changedHandlerNameToPropertyName(propertyName);
+ actualPropertyName && m_objectType->hasProperty(*actualPropertyName)) {
+ function.isSignalHandler = true;
+ } else {
+ auto signalName = QQmlSignalNames::handlerNameToSignalName(propertyName);
+ const auto methods = m_objectType->methods(*signalName);
+ for (const auto &method : methods) {
+ if (method.isCloned())
+ continue;
+ if (method.methodType() == QQmlJSMetaMethodType::Signal) {
+ function.isSignalHandler = true;
+ const auto arguments = method.parameters();
+ for (qsizetype i = 0, end = arguments.size(); i < end; ++i) {
+ const auto &type = arguments[i].type();
+ if (type.isNull()) {
+ diagnose(u"Cannot resolve the argument type %1."_s.arg(
+ arguments[i].typeName()),
+ QtDebugMsg, bindingLocation, error);
+ function.argumentTypes.append(
m_typeResolver->tracked(
- m_typeResolver->globalType(m_typeResolver->varType())));
- } else {
- function.argumentTypes.append(m_typeResolver->tracked(
- m_typeResolver->globalType(type)));
+ m_typeResolver->globalType(m_typeResolver->varType())));
+ } else {
+ function.argumentTypes.append(m_typeResolver->tracked(
+ m_typeResolver->globalType(type)));
+ }
}
+ break;
}
- break;
+ }
+ if (!function.isSignalHandler) {
+ diagnose(u"Could not compile signal handler for %1: The signal does not exist"_s.arg(
+ *signalName),
+ QtWarningMsg, bindingLocation, error);
}
}
}
-
- if (!function.isSignalHandler) {
- diagnose(u"Could not compile signal handler for %1: The signal does not exist"_s.arg(
- signalName),
- QtWarningMsg, bindingLocation, error);
- }
}
if (!function.isSignalHandler) {
@@ -209,13 +219,19 @@ QQmlJSCompilePass::Function QQmlJSFunctionInitializer::run(
const auto property = m_objectType->property(propertyName);
if (const QQmlJSScope::ConstPtr propertyType = property.type()) {
- function.returnType = propertyType->isListProperty()
- ? m_typeResolver->qObjectListType()
- : propertyType;
+ function.returnType = m_typeResolver->globalType(propertyType->isListProperty()
+ ? m_typeResolver->qObjectListType()
+ : QQmlJSScope::ConstPtr(property.type()));
} else {
- diagnose(u"Cannot resolve property type %1 for binding on %2"_s.arg(
- property.typeName(), propertyName),
- QtWarningMsg, bindingLocation, error);
+ QString message = u"Cannot resolve property type %1 for binding on %2."_s
+ .arg(property.typeName(), propertyName);
+ if (m_objectType->isNameDeferred(propertyName)) {
+ // If the property doesn't exist but the name is deferred, then
+ // it's deferred via the presence of immediate names. Those are
+ // most commonly used to enable generalized grouped properties.
+ message += u" You may want use ID-based grouped properties here.";
+ }
+ diagnose(message, QtWarningMsg, bindingLocation, error);
}
if (!property.bindable().isEmpty() && !property.isPrivate())
@@ -254,7 +270,7 @@ QQmlJSCompilePass::Function QQmlJSFunctionInitializer::run(
Q_UNUSED(functionName);
QQmlJSCompilePass::Function function;
- function.qmlScope = m_scopeType;
+ function.qmlScope = m_typeResolver->globalType(m_scopeType);
auto ast = astNode->asFunctionDefinition();
Q_ASSERT(ast);
diff --git a/src/qmlcompiler/qqmljsfunctioninitializer_p.h b/src/qmlcompiler/qqmljsfunctioninitializer_p.h
index 9f191a4af8..424959a4fa 100644
--- a/src/qmlcompiler/qqmljsfunctioninitializer_p.h
+++ b/src/qmlcompiler/qqmljsfunctioninitializer_p.h
@@ -18,7 +18,7 @@
QT_BEGIN_NAMESPACE
-class Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSFunctionInitializer
+class Q_QMLCOMPILER_EXPORT QQmlJSFunctionInitializer
{
Q_DISABLE_COPY_MOVE(QQmlJSFunctionInitializer)
public:
diff --git a/src/qmlcompiler/qqmljsimporter.cpp b/src/qmlcompiler/qqmljsimporter.cpp
index 2b42941039..ae5d1654f0 100644
--- a/src/qmlcompiler/qqmljsimporter.cpp
+++ b/src/qmlcompiler/qqmljsimporter.cpp
@@ -16,8 +16,24 @@ QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
-static const QLatin1String SlashQmldir = QLatin1String("/qmldir");
-static const QLatin1String SlashPluginsDotQmltypes = QLatin1String("/plugins.qmltypes");
+static const QLatin1String SlashQmldir = QLatin1String("/qmldir");
+static const QLatin1String PluginsDotQmltypes = QLatin1String("plugins.qmltypes");
+
+
+QQmlJS::Import::Import(QString prefix, QString name, QTypeRevision version, bool isFile,
+ bool isDependency) :
+ m_prefix(std::move(prefix)),
+ m_name(std::move(name)),
+ m_version(version),
+ m_isFile(isFile),
+ m_isDependency(isDependency)
+{
+}
+
+bool QQmlJS::Import::isValid() const
+{
+ return !m_name.isEmpty();
+}
static const QString prefixedName(const QString &prefix, const QString &name)
{
@@ -25,12 +41,21 @@ static const QString prefixedName(const QString &prefix, const QString &name)
return prefix.isEmpty() ? name : (prefix + QLatin1Char('.') + name);
}
-static QQmlDirParser createQmldirParserForFile(const QString &filename)
+QQmlDirParser QQmlJSImporter::createQmldirParserForFile(const QString &filename)
{
QFile f(filename);
- f.open(QFile::ReadOnly);
QQmlDirParser parser;
- parser.parse(QString::fromUtf8(f.readAll()));
+ if (f.open(QFile::ReadOnly)) {
+ parser.parse(QString::fromUtf8(f.readAll()));
+ } else {
+ m_warnings.append({
+ QStringLiteral("Could not open qmldir file: ")
+ + filename,
+ QtWarningMsg,
+ QQmlJS::SourceLocation()
+ });
+ }
+
return parser;
}
@@ -129,24 +154,79 @@ static bool isComposite(const QQmlJSScope::ConstPtr &scope)
return scope.factory() || scope->isComposite();
}
+static QStringList aliases(const QQmlJSScope::ConstPtr &scope)
+{
+ return isComposite(scope)
+ ? QStringList()
+ : scope->aliases();
+}
+
QQmlJSImporter::QQmlJSImporter(const QStringList &importPaths, QQmlJSResourceFileMapper *mapper,
bool useOptionalImports)
: m_importPaths(importPaths),
m_mapper(mapper),
m_useOptionalImports(useOptionalImports),
- m_createImportVisitor([](const QQmlJSScope::Ptr &target, QQmlJSImporter *importer,
- QQmlJSLogger *logger, const QString &implicitImportDirectory,
- const QStringList &qmldirFiles) {
- return new QQmlJSImportVisitor(target, importer, logger, implicitImportDirectory,
- qmldirFiles);
+ m_importVisitor([](QQmlJS::AST::Node *rootNode, QQmlJSImporter *self,
+ const ImportVisitorPrerequisites &p) {
+ auto visitor = std::unique_ptr<QQmlJS::AST::BaseVisitor>(new QQmlJSImportVisitor(
+ p.m_target, self, p.m_logger, p.m_implicitImportDirectory, p.m_qmldirFiles));
+ QQmlJS::AST::Node::accept(rootNode, visitor.get());
})
{
}
-QQmlJSImporter::Import QQmlJSImporter::readQmldir(const QString &path)
+static QString resolvePreferredPath(
+ const QString &qmldirPath, const QString &prefer, QQmlJSResourceFileMapper *mapper)
{
+ if (prefer.isEmpty())
+ return qmldirPath;
+
+ if (!prefer.endsWith(u'/')) {
+ qWarning() << "Ignoring invalid prefer path" << prefer << "(has to end with slash)";
+ return qmldirPath;
+ }
+
+ if (prefer.startsWith(u':')) {
+ // Resource path: Resolve via resource file mapper if possible.
+ if (!mapper)
+ return qmldirPath;
+
+ Q_ASSERT(prefer.endsWith(u'/'));
+ const auto entry = mapper->entry(
+ QQmlJSResourceFileMapper::resourceFileFilter(prefer.mid(1) + SlashQmldir.mid(1)));
+
+ // This can be empty if the .qrc files does not belong to this module.
+ // In that case we trust the given qmldir file.
+ return entry.filePath.endsWith(SlashQmldir)
+ ? entry.filePath
+ : qmldirPath;
+ }
+
+ // Host file system path. This should be rare. We don't generate it.
+ const QFileInfo f(prefer + SlashQmldir);
+ const QString canonical = f.canonicalFilePath();
+ if (canonical.isEmpty()) {
+ qWarning() << "No qmldir at" << prefer;
+ return qmldirPath;
+ }
+ return canonical;
+}
+
+QQmlJSImporter::Import QQmlJSImporter::readQmldir(const QString &modulePath)
+{
+ const QString moduleQmldirPath = modulePath + SlashQmldir;
+ auto reader = createQmldirParserForFile(moduleQmldirPath);
+
+ const QString resolvedQmldirPath
+ = resolvePreferredPath(moduleQmldirPath, reader.preferredPath(), m_mapper);
+ if (resolvedQmldirPath != moduleQmldirPath)
+ reader = createQmldirParserForFile(resolvedQmldirPath);
+
+ // Leave the trailing slash
+ Q_ASSERT(resolvedQmldirPath.endsWith(SlashQmldir));
+ QStringView resolvedPath = QStringView(resolvedQmldirPath).chopped(SlashQmldir.size() - 1);
+
Import result;
- auto reader = createQmldirParserForFile(path + SlashQmldir);
result.name = reader.typeNamespace();
result.isStaticModule = reader.isStaticModule();
@@ -157,12 +237,13 @@ QQmlJSImporter::Import QQmlJSImporter::readQmldir(const QString &path)
const auto typeInfos = reader.typeInfos();
for (const auto &typeInfo : typeInfos) {
const QString typeInfoPath = QFileInfo(typeInfo).isRelative()
- ? path + u'/' + typeInfo : typeInfo;
+ ? resolvedPath + typeInfo
+ : typeInfo;
readQmltypes(typeInfoPath, &result.objects, &result.dependencies);
}
if (typeInfos.isEmpty() && !reader.plugins().isEmpty()) {
- const QString defaultTypeInfoPath = path + SlashPluginsDotQmltypes;
+ const QString defaultTypeInfoPath = resolvedPath + PluginsDotQmltypes;
if (QFile::exists(defaultTypeInfoPath)) {
m_warnings.append({
QStringLiteral("typeinfo not declared in qmldir file: ")
@@ -177,11 +258,11 @@ QQmlJSImporter::Import QQmlJSImporter::readQmldir(const QString &path)
QHash<QString, QQmlJSExportedScope> qmlComponents;
const auto components = reader.components();
for (auto it = components.begin(), end = components.end(); it != end; ++it) {
- const QString filePath = path + QLatin1Char('/') + it->fileName;
+ const QString filePath = resolvedPath + it->fileName;
if (!QFile::exists(filePath)) {
m_warnings.append({
it->fileName + QStringLiteral(" is listed as component in ")
- + path + SlashQmldir
+ + resolvedQmldirPath
+ QStringLiteral(" but does not exist.\n"),
QtWarningMsg,
QQmlJS::SourceLocation()
@@ -208,7 +289,7 @@ QQmlJSImporter::Import QQmlJSImporter::readQmldir(const QString &path)
const auto scripts = reader.scripts();
for (const auto &script : scripts) {
- const QString filePath = path + QLatin1Char('/') + script.fileName;
+ const QString filePath = resolvedPath + script.fileName;
auto mo = result.scripts.find(script.fileName);
if (mo == result.scripts.end())
mo = result.scripts.insert(script.fileName, { localFile2ScopeTree(filePath), {} });
@@ -308,7 +389,7 @@ void QQmlJSImporter::importDependencies(const QQmlJSImporter::Import &import,
}
static bool isVersionAllowed(const QQmlJSScope::Export &exportEntry,
- const QQmlJSScope::Import &importDescription)
+ const QQmlJS::Import &importDescription)
{
const QTypeRevision importVersion = importDescription.version();
const QTypeRevision exportVersion = exportEntry.version();
@@ -320,7 +401,7 @@ static bool isVersionAllowed(const QQmlJSScope::Export &exportEntry,
|| exportVersion.minorVersion() <= importVersion.minorVersion();
}
-void QQmlJSImporter::processImport(const QQmlJSScope::Import &importDescription,
+void QQmlJSImporter::processImport(const QQmlJS::Import &importDescription,
const QQmlJSImporter::Import &import,
QQmlJSImporter::AvailableTypes *types)
{
@@ -333,6 +414,12 @@ void QQmlJSImporter::processImport(const QQmlJSScope::Import &importDescription,
const QString modulePrefix = QStringLiteral("$module$");
QHash<QString, QList<QQmlJSScope::Export>> seenExports;
+ const auto insertAliases = [&](const QQmlJSScope::ConstPtr &scope, QTypeRevision revision) {
+ const QStringList cppAliases = aliases(scope);
+ for (const QString &alias : cppAliases)
+ types->cppNames.setType(alias, { scope, revision });
+ };
+
const auto insertExports = [&](const QQmlJSExportedScope &val, const QString &cppName) {
QQmlJSScope::Export bestExport;
@@ -405,6 +492,8 @@ void QQmlJSImporter::processImport(const QQmlJSScope::Import &importDescription,
: QTypeRevision::zero();
types->cppNames.setType(cppName, { val.scope, bestRevision });
+ insertAliases(val.scope, bestRevision);
+
const QTypeRevision bestVersion = bestExport.isValid()
? bestExport.version()
: QTypeRevision::zero();
@@ -444,6 +533,7 @@ void QQmlJSImporter::processImport(const QQmlJSScope::Import &importDescription,
types->qmlNames.setType(
prefixedName(internalPrefix, cppName), { val.scope, QTypeRevision() });
types->cppNames.setType(cppName, { val.scope, QTypeRevision() });
+ insertAliases(val.scope, QTypeRevision());
} else {
insertExports(val, cppName);
}
@@ -478,11 +568,10 @@ void QQmlJSImporter::processImport(const QQmlJSScope::Import &importDescription,
// only happen when enumerations are involved, thus the strategy is to
// resolve enumerations (which can potentially create new child scopes)
// before resolving the type fully
- const QQmlJSScope::ConstPtr intType = tempTypes.cppNames.type(u"int"_s).scope;
const QQmlJSScope::ConstPtr arrayType = tempTypes.cppNames.type(u"Array"_s).scope;
for (auto it = import.objects.begin(); it != import.objects.end(); ++it) {
if (!it->scope.factory()) {
- QQmlJSScope::resolveEnums(it->scope, intType);
+ QQmlJSScope::resolveEnums(it->scope, tempTypes.cppNames);
QQmlJSScope::resolveList(it->scope, arrayType);
}
}
@@ -522,29 +611,35 @@ QQmlJSImporter::AvailableTypes QQmlJSImporter::builtinImportHelper()
if (m_builtins)
return *m_builtins;
- AvailableTypes builtins(ImportedTypes(ImportedTypes::INTERNAL, {}, {}, {}));
+ AvailableTypes builtins(ImportedTypes(ImportedTypes::INTERNAL, {}, {}));
Import result;
result.name = QStringLiteral("QML");
- QStringList qmltypesFiles = { QStringLiteral("builtins.qmltypes"),
- QStringLiteral("jsroot.qmltypes") };
- const auto importBuiltins = [&](const QStringList &imports) {
+ const auto importBuiltins = [&](const QString &qmltypesFile, const QStringList &imports) {
for (auto const &dir : imports) {
- QDirIterator it { dir, qmltypesFiles, QDir::NoFilter, QDirIterator::Subdirectories };
- while (it.hasNext() && !qmltypesFiles.isEmpty()) {
- readQmltypes(it.next(), &result.objects, &result.dependencies);
- qmltypesFiles.removeOne(it.fileName());
- }
+ const QDir importDir(dir);
+ if (!importDir.exists(qmltypesFile))
+ continue;
+
+ readQmltypes(
+ importDir.filePath(qmltypesFile), &result.objects, &result.dependencies);
setQualifiedNamesOn(result);
importDependencies(result, &builtins);
-
- if (qmltypesFiles.isEmpty())
- return;
+ return true;
}
+
+ return false;
};
- importBuiltins(m_importPaths);
+ // If the same name (such as "Qt") appears in the JS root and in the builtins,
+ // we want the builtins to override the JS root. Therefore, process jsroot first.
+ QStringList qmltypesFiles;
+ for (QString qmltypesFile : { "jsroot.qmltypes"_L1, "builtins.qmltypes"_L1 }) {
+ if (!importBuiltins(qmltypesFile, m_importPaths))
+ qmltypesFiles.append(std::move(qmltypesFile));
+ }
+
if (!qmltypesFiles.isEmpty()) {
const QString pathsString =
m_importPaths.isEmpty() ? u"<empty>"_s : m_importPaths.join(u"\n\t");
@@ -552,13 +647,17 @@ QQmlJSImporter::AvailableTypes QQmlJSImporter::builtinImportHelper()
"qrc). Import paths used:\n\t%2")
.arg(qmltypesFiles.join(u", "), pathsString),
QtWarningMsg, QQmlJS::SourceLocation() });
- importBuiltins({ u":/qt-project.org/qml/builtins"_s }); // use qrc as a "last resort"
+
+ // use qrc as a "last resort"
+ for (const QString &qmltypesFile : std::as_const(qmltypesFiles)) {
+ const bool found = importBuiltins(qmltypesFile, { u":/qt-project.org/qml/builtins"_s });
+ Q_ASSERT(found); // since qrc must cover it in all the bad cases
+ }
}
- Q_ASSERT(qmltypesFiles.isEmpty()); // since qrc must cover it in all the bad cases
// Process them together since there they have interdependencies that wouldn't get resolved
// otherwise
- const QQmlJSScope::Import builtinImport(
+ const QQmlJS::Import builtinImport(
QString(), QStringLiteral("QML"), QTypeRevision::fromVersion(1, 0), false, true);
QQmlJSScope::ConstPtr intType;
@@ -579,12 +678,10 @@ QQmlJSImporter::AvailableTypes QQmlJSImporter::builtinImportHelper()
Q_ASSERT(intType);
Q_ASSERT(arrayType);
- m_builtins = AvailableTypes(ImportedTypes(
- ImportedTypes::INTERNAL, builtins.cppNames.types(),
- intType, arrayType));
- m_builtins->qmlNames =
- ImportedTypes(ImportedTypes::QML, builtins.qmlNames.types(),
- intType, arrayType);
+ m_builtins = AvailableTypes(
+ ImportedTypes(ImportedTypes::INTERNAL, builtins.cppNames.types(), arrayType));
+ m_builtins->qmlNames
+ = ImportedTypes(ImportedTypes::QML, builtins.qmlNames.types(), arrayType);
processImport(builtinImport, result, &(*m_builtins));
@@ -601,6 +698,7 @@ void QQmlJSImporter::importQmldirs(const QStringList &qmldirFiles)
QString qmldirName;
if (file.endsWith(SlashQmldir)) {
result = readQmldir(file.chopped(SlashQmldir.size()));
+ setQualifiedNamesOn(result);
qmldirName = file;
} else {
m_warnings.append({
@@ -674,7 +772,7 @@ bool QQmlJSImporter::importHelper(const QString &module, AvailableTypes *types,
if (isDependency)
Q_ASSERT(prefix.isEmpty());
- const QQmlJSScope::Import cacheKey(prefix, moduleCacheName, version, isFile, isDependency);
+ const QQmlJS::Import cacheKey(prefix, moduleCacheName, version, isFile, isDependency);
auto getTypesFromCache = [&]() -> bool {
if (!m_cachedImportTypes.contains(cacheKey))
@@ -703,9 +801,7 @@ bool QQmlJSImporter::importHelper(const QString &module, AvailableTypes *types,
auto cacheTypes = QSharedPointer<QQmlJSImporter::AvailableTypes>(
new QQmlJSImporter::AvailableTypes(
- ImportedTypes(
- ImportedTypes::INTERNAL, {},
- types->cppNames.intType(), types->cppNames.arrayType())));
+ ImportedTypes(ImportedTypes::INTERNAL, {}, types->cppNames.arrayType())));
m_cachedImportTypes[cacheKey] = cacheTypes;
const QPair<QString, QTypeRevision> importId { module, version };
@@ -822,8 +918,7 @@ QQmlJSImporter::ImportedTypes QQmlJSImporter::importDirectory(
const AvailableTypes builtins = builtinImportHelper();
QQmlJSImporter::AvailableTypes types(
ImportedTypes(
- ImportedTypes::INTERNAL, {},
- builtins.cppNames.intType(), builtins.cppNames.arrayType()));
+ ImportedTypes::INTERNAL, {}, builtins.cppNames.arrayType()));
importHelper(directory, &types, prefix, QTypeRevision(), false, true);
return types.qmlNames;
}
@@ -857,27 +952,18 @@ void QQmlJSImporter::setQualifiedNamesOn(const Import &import)
for (auto &object : import.objects) {
if (object.exports.isEmpty())
continue;
- const QString qualifiedName = QQmlJSScope::qualifiedNameFrom(
- import.name, object.exports.first().type(),
- object.exports.first().revision(),
- object.exports.last().revision());
if (auto *factory = object.scope.factory()) {
- factory->setQualifiedName(qualifiedName);
factory->setModuleName(import.name);
} else {
- object.scope->setQualifiedName(qualifiedName);
- object.scope->setModuleName(import.name);
+ object.scope->setOwnModuleName(import.name);
}
}
}
-std::unique_ptr<QQmlJSImportVisitor>
-QQmlJSImporter::makeImportVisitor(const QQmlJSScope::Ptr &target, QQmlJSImporter *importer,
- QQmlJSLogger *logger, const QString &implicitImportDirectory,
- const QStringList &qmldirFiles)
+void QQmlJSImporter::runImportVisitor(QQmlJS::AST::Node *rootNode,
+ const ImportVisitorPrerequisites &p)
{
- return std::unique_ptr<QQmlJSImportVisitor>(
- m_createImportVisitor(target, importer, logger, implicitImportDirectory, qmldirFiles));
+ m_importVisitor(rootNode, this, p);
}
QT_END_NAMESPACE
diff --git a/src/qmlcompiler/qqmljsimporter_p.h b/src/qmlcompiler/qqmljsimporter_p.h
index 792e26a291..290ea71605 100644
--- a/src/qmlcompiler/qqmljsimporter_p.h
+++ b/src/qmlcompiler/qqmljsimporter_p.h
@@ -14,22 +14,60 @@
//
// We mean it.
-#include <private/qtqmlcompilerexports_p.h>
+#include <qtqmlcompilerexports.h>
+#include "qqmljscontextualtypes_p.h"
#include "qqmljsscope_p.h"
#include "qqmljsresourcefilemapper_p.h"
#include <QtQml/private/qqmldirparser_p.h>
+#include <QtQml/private/qqmljsast_p.h>
#include <memory>
QT_BEGIN_NAMESPACE
+namespace QQmlJS {
+class Import
+{
+public:
+ Import() = default;
+ Import(QString prefix, QString name, QTypeRevision version, bool isFile, bool isDependency);
+
+ bool isValid() const;
+
+ QString prefix() const { return m_prefix; }
+ QString name() const { return m_name; }
+ QTypeRevision version() const { return m_version; }
+ bool isFile() const { return m_isFile; }
+ bool isDependency() const { return m_isDependency; }
+
+private:
+ QString m_prefix;
+ QString m_name;
+ QTypeRevision m_version;
+ bool m_isFile = false;
+ bool m_isDependency = false;
+
+ friend inline size_t qHash(const Import &key, size_t seed = 0) noexcept
+ {
+ return qHashMulti(seed, key.m_prefix, key.m_name, key.m_version,
+ key.m_isFile, key.m_isDependency);
+ }
+
+ friend inline bool operator==(const Import &a, const Import &b)
+ {
+ return a.m_prefix == b.m_prefix && a.m_name == b.m_name && a.m_version == b.m_version
+ && a.m_isFile == b.m_isFile && a.m_isDependency == b.m_isDependency;
+ }
+};
+}
+
class QQmlJSImportVisitor;
class QQmlJSLogger;
-class Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSImporter
+class Q_QMLCOMPILER_EXPORT QQmlJSImporter
{
public:
- using ImportedTypes = QQmlJSScope::ContextualTypes;
+ using ImportedTypes = QQmlJS::ContextualTypes;
QQmlJSImporter(const QStringList &importPaths, QQmlJSResourceFileMapper *mapper,
bool useOptionalImports = false);
@@ -76,14 +114,38 @@ public:
QQmlJSScope::ConstPtr jsGlobalObject() const;
- std::unique_ptr<QQmlJSImportVisitor>
- makeImportVisitor(const QQmlJSScope::Ptr &target, QQmlJSImporter *importer,
- QQmlJSLogger *logger, const QString &implicitImportDirectory,
- const QStringList &qmldirFiles = QStringList());
- using ImportVisitorCreator = QQmlJSImportVisitor *(*)(const QQmlJSScope::Ptr &,
- QQmlJSImporter *, QQmlJSLogger *,
- const QString &, const QStringList &);
- void setImportVisitorCreator(ImportVisitorCreator create) { m_createImportVisitor = create; }
+ struct ImportVisitorPrerequisites
+ {
+ ImportVisitorPrerequisites(QQmlJSScope::Ptr target, QQmlJSLogger *logger,
+ const QString &implicitImportDirectory = {},
+ const QStringList &qmldirFiles = {})
+ : m_target(target),
+ m_logger(logger),
+ m_implicitImportDirectory(implicitImportDirectory),
+ m_qmldirFiles(qmldirFiles)
+ {
+ Q_ASSERT(target && logger);
+ }
+
+ QQmlJSScope::Ptr m_target;
+ QQmlJSLogger *m_logger;
+ QString m_implicitImportDirectory;
+ QStringList m_qmldirFiles;
+ };
+ void runImportVisitor(QQmlJS::AST::Node *rootNode,
+ const ImportVisitorPrerequisites &prerequisites);
+
+ /*!
+ \internal
+ When a qml file gets lazily loaded, it will be lexed and parsed and finally be constructed
+ via an ImportVisitor. By default, this is done via the QQmlJSImportVisitor, but can also be done
+ via other import visitors like QmltcVisitor, which is used by qmltc to compile a QML file, or
+ QQmlDomAstCreatorWithQQmlJSScope, which is used to construct the Dom of lazily loaded QML files.
+ */
+ using ImportVisitor = std::function<void(QQmlJS::AST::Node *rootNode, QQmlJSImporter *self,
+ const ImportVisitorPrerequisites &prerequisites)>;
+
+ void setImportVisitor(ImportVisitor visitor) { m_importVisitor = visitor; }
private:
friend class QDeferredFactory<QQmlJSScope>;
@@ -92,8 +154,7 @@ private:
{
AvailableTypes(ImportedTypes builtins)
: cppNames(std::move(builtins))
- , qmlNames(QQmlJSScope::ContextualTypes::QML, {},
- cppNames.intType(), cppNames.arrayType())
+ , qmlNames(QQmlJS::ContextualTypes::QML, {}, cppNames.arrayType())
{
}
@@ -125,11 +186,12 @@ private:
bool importHelper(const QString &module, AvailableTypes *types,
const QString &prefix = QString(), QTypeRevision version = QTypeRevision(),
bool isDependency = false, bool isFile = false);
- void processImport(const QQmlJSScope::Import &importDescription, const Import &import,
+ void processImport(const QQmlJS::Import &importDescription, const Import &import,
AvailableTypes *types);
void importDependencies(const QQmlJSImporter::Import &import, AvailableTypes *types,
const QString &prefix = QString(),
QTypeRevision version = QTypeRevision(), bool isDependency = false);
+ QQmlDirParser createQmldirParserForFile(const QString &filename);
void readQmltypes(const QString &filename, QList<QQmlJSExportedScope> *objects,
QList<QQmlDirParser::Import> *dependencies);
Import readQmldir(const QString &dirname);
@@ -141,7 +203,7 @@ private:
QStringList m_importPaths;
QHash<QPair<QString, QTypeRevision>, QString> m_seenImports;
- QHash<QQmlJSScope::Import, QSharedPointer<AvailableTypes>> m_cachedImportTypes;
+ QHash<QQmlJS::Import, QSharedPointer<AvailableTypes>> m_cachedImportTypes;
QHash<QString, Import> m_seenQmldirFiles;
QHash<QString, QQmlJSScope::Ptr> m_importedFiles;
@@ -153,7 +215,7 @@ private:
QQmlJSResourceFileMapper *m_metaDataMapper = nullptr;
bool m_useOptionalImports;
- ImportVisitorCreator m_createImportVisitor = nullptr;
+ ImportVisitor m_importVisitor;
};
QT_END_NAMESPACE
diff --git a/src/qmlcompiler/qqmljsimportvisitor.cpp b/src/qmlcompiler/qqmljsimportvisitor.cpp
index 48c4bcbeef..f048748b58 100644
--- a/src/qmlcompiler/qqmljsimportvisitor.cpp
+++ b/src/qmlcompiler/qqmljsimportvisitor.cpp
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "qqmljsimportvisitor_p.h"
+#include "qqmljslogger_p.h"
#include "qqmljsmetatypes_p.h"
#include "qqmljsresourcefilemapper_p.h"
@@ -13,13 +14,18 @@
#include <QtCore/qrect.h>
#include <QtCore/qsize.h>
+#include <QtQml/private/qqmlsignalnames_p.h>
#include <QtQml/private/qv4codegen_p.h>
#include <QtQml/private/qqmlstringconverters_p.h>
#include <QtQml/private/qqmlirbuilder_p.h>
#include "qqmljsscope_p.h"
#include "qqmljsutils_p.h"
+#include "qqmljsloggingutils.h"
+#include "qqmlsaconstants.h"
#include <algorithm>
+#include <limits>
+#include <optional>
#include <variant>
QT_BEGIN_NAMESPACE
@@ -29,13 +35,56 @@ using namespace Qt::StringLiterals;
using namespace QQmlJS::AST;
/*!
+ \internal
+ Returns if assigning \a assignedType to \a property would require an
+ implicit component wrapping.
+ */
+static bool causesImplicitComponentWrapping(const QQmlJSMetaProperty &property,
+ const QQmlJSScope::ConstPtr &assignedType)
+{
+ // See QQmlComponentAndAliasResolver::findAndRegisterImplicitComponents()
+ // for the logic in qqmltypecompiler
+
+ // Note: unlike findAndRegisterImplicitComponents() we do not check whether
+ // the property type is *derived* from QQmlComponent at some point because
+ // this is actually meaningless (and in the case of QQmlComponent::create()
+ // gets rejected in QQmlPropertyValidator): if the type is not a
+ // QQmlComponent, we have a type mismatch because of assigning a Component
+ // object to a non-Component property
+ const bool propertyVerdict = property.type()->internalName() == u"QQmlComponent";
+
+ const bool assignedTypeVerdict = [&assignedType]() {
+ // Note: nonCompositeBaseType covers the case when assignedType itself
+ // is non-composite
+ auto cppBase = QQmlJSScope::nonCompositeBaseType(assignedType);
+ Q_ASSERT(cppBase); // any QML type has (or must have) a C++ base type
+
+ // See isUsableComponent() in qqmltypecompiler.cpp: along with checking
+ // whether a type has a QQmlComponent static meta object (which we
+ // substitute here with checking the first non-composite base for being
+ // a QQmlComponent), it also excludes QQmlAbstractDelegateComponent
+ // subclasses from implicit wrapping
+ if (cppBase->internalName() == u"QQmlComponent")
+ return false;
+ for (; cppBase; cppBase = cppBase->baseType()) {
+ if (cppBase->internalName() == u"QQmlAbstractDelegateComponent")
+ return false;
+ }
+ return true;
+ }();
+
+ return propertyVerdict && assignedTypeVerdict;
+}
+
+/*!
\internal
Sets the name of \a scope to \a name based on \a type.
*/
inline void setScopeName(QQmlJSScope::Ptr &scope, QQmlJSScope::ScopeType type, const QString &name)
{
Q_ASSERT(scope);
- if (type == QQmlJSScope::GroupedPropertyScope || type == QQmlJSScope::AttachedPropertyScope)
+ if (type == QQmlSA::ScopeType::GroupedPropertyScope
+ || type == QQmlSA::ScopeType::AttachedPropertyScope)
scope->setInternalName(name);
else
scope->setBaseTypeName(name);
@@ -48,7 +97,8 @@ inline void setScopeName(QQmlJSScope::Ptr &scope, QQmlJSScope::ScopeType type, c
inline QString getScopeName(const QQmlJSScope::ConstPtr &scope, QQmlJSScope::ScopeType type)
{
Q_ASSERT(scope);
- if (type == QQmlJSScope::GroupedPropertyScope || type == QQmlJSScope::AttachedPropertyScope)
+ if (type == QQmlSA::ScopeType::GroupedPropertyScope
+ || type == QQmlSA::ScopeType::AttachedPropertyScope)
return scope->internalName();
return scope->baseTypeName();
@@ -77,10 +127,9 @@ QQmlJSImportVisitor::QQmlJSImportVisitor(
m_logger(logger),
m_rootScopeImports(
QQmlJSImporter::ImportedTypes::QML, {},
- importer->builtinInternalNames().intType(),
importer->builtinInternalNames().arrayType())
{
- m_currentScope->setScopeType(QQmlJSScope::JSFunctionScope);
+ m_currentScope->setScopeType(QQmlSA::ScopeType::JSFunctionScope);
Q_ASSERT(logger); // must be valid
m_globalScope = m_currentScope;
@@ -102,7 +151,8 @@ QQmlJSImportVisitor::QQmlJSImportVisitor(
};
QQmlJSScope::JavaScriptIdentifier globalJavaScript = {
- QQmlJSScope::JavaScriptIdentifier::LexicalScoped, QQmlJS::SourceLocation(), true
+ QQmlJSScope::JavaScriptIdentifier::LexicalScoped, QQmlJS::SourceLocation(), std::nullopt,
+ true
};
for (const char **globalName = QV4::Compiler::Codegen::s_globalNames; *globalName != nullptr;
++globalName) {
@@ -145,8 +195,8 @@ bool QQmlJSImportVisitor::enterEnvironmentNonUnique(QQmlJSScope::ScopeType type,
const QString &name,
const QQmlJS::SourceLocation &location)
{
- Q_ASSERT(type == QQmlJSScope::GroupedPropertyScope
- || type == QQmlJSScope::AttachedPropertyScope);
+ Q_ASSERT(type == QQmlSA::ScopeType::GroupedPropertyScope
+ || type == QQmlSA::ScopeType::AttachedPropertyScope);
const auto pred = [&](const QQmlJSScope::ConstPtr &s) {
// it's either attached or group property, so use internalName()
@@ -185,7 +235,7 @@ bool QQmlJSImportVisitor::isTypeResolved(const QQmlJSScope::ConstPtr &type)
static bool mayBeUnresolvedGeneralizedGroupedProperty(const QQmlJSScope::ConstPtr &scope)
{
- return scope->scopeType() == QQmlJSScope::GroupedPropertyScope && !scope->baseType();
+ return scope->scopeType() == QQmlSA::ScopeType::GroupedPropertyScope && !scope->baseType();
}
void QQmlJSImportVisitor::resolveAliasesAndIds()
@@ -263,7 +313,7 @@ void QQmlJSImportVisitor::resolveAliasesAndIds()
newProperty.setIsWritable(targetProperty.isWritable());
newProperty.setIsPointer(targetProperty.isPointer());
- if (!typeScope.isNull()) {
+ if (!typeScope.isNull() && !object->isPropertyLocallyRequired(property.propertyName())) {
object->setPropertyLocallyRequired(
newProperty.propertyName(),
typeScope->isPropertyRequired(targetProperty.propertyName()));
@@ -360,7 +410,7 @@ void QQmlJSImportVisitor::importBaseModules()
// Pulling in the modules and neighboring qml files of the qmltypes we're trying to lint is not
// something we need to do.
if (!m_logger->fileName().endsWith(u".qmltypes"_s)) {
- QQmlJSScope::ContextualTypes fromDirectory =
+ QQmlJS::ContextualTypes fromDirectory =
m_importer->importDirectory(m_implicitImportDirectory);
m_rootScopeImports.addTypes(std::move(fromDirectory));
@@ -415,6 +465,7 @@ void QQmlJSImportVisitor::endVisit(UiProgram *)
setAllBindings();
processDefaultProperties();
processPropertyTypes();
+ processMethodTypes();
processPropertyBindings();
processPropertyBindingObjects();
checkRequiredProperties();
@@ -578,8 +629,7 @@ void QQmlJSImportVisitor::processDefaultProperties()
if (!isTypeResolved(propType, handleUnresolvedDefaultProperty))
continue;
- const auto scopes = *it;
- for (const auto &scope : scopes) {
+ for (const QQmlJSScope::Ptr &scope : std::as_const(*it)) {
if (!isTypeResolved(scope))
continue;
@@ -587,7 +637,7 @@ void QQmlJSImportVisitor::processDefaultProperties()
// Check whether the property can be assigned the scope
if (propType->canAssign(scope)) {
scope->setIsWrappedInImplicitComponent(
- QQmlJSScope::causesImplicitComponentWrapping(defaultProp, scope));
+ causesImplicitComponentWrapping(defaultProp, scope));
continue;
}
@@ -616,6 +666,39 @@ void QQmlJSImportVisitor::processPropertyTypes()
}
}
+void QQmlJSImportVisitor::processMethodTypes()
+{
+ for (const auto &type : m_pendingMethodTypes) {
+
+ for (auto [it, end] = type.scope->mutableOwnMethodsRange(type.methodName); it != end;
+ ++it) {
+ if (const auto returnType =
+ QQmlJSScope::findType(it->returnTypeName(), m_rootScopeImports).scope) {
+ it->setReturnType({ returnType });
+ } else {
+ m_logger->log(u"\"%1\" was not found for the return type of method \"%2\"."_s.arg(
+ it->returnTypeName(), it->methodName()),
+ qmlUnresolvedType, type.location);
+ }
+
+ for (auto [parameter, parameterEnd] = it->mutableParametersRange();
+ parameter != parameterEnd; ++parameter) {
+ if (const auto parameterType =
+ QQmlJSScope::findType(parameter->typeName(), m_rootScopeImports)
+ .scope) {
+ parameter->setType({ parameterType });
+ } else {
+ m_logger->log(
+ u"\"%1\" was not found for the type of parameter \"%2\" in method \"%3\"."_s
+ .arg(parameter->typeName(), parameter->name(),
+ it->methodName()),
+ qmlUnresolvedType, type.location);
+ }
+ }
+ }
+ }
+}
+
void QQmlJSImportVisitor::processPropertyBindingObjects()
{
QSet<QPair<QQmlJSScope::Ptr, QString>> foundLiterals;
@@ -689,17 +772,17 @@ void QQmlJSImportVisitor::processPropertyBindingObjects()
"incompatible type \"%3\"")
.arg(propertyName)
.arg(property.typeName())
- .arg(getScopeName(childScope, QQmlJSScope::QMLScope)),
+ .arg(getScopeName(childScope, QQmlSA::ScopeType::QMLScope)),
qmlIncompatibleType, objectBinding.location);
continue;
}
objectBinding.childScope->setIsWrappedInImplicitComponent(
- QQmlJSScope::causesImplicitComponentWrapping(property, childScope));
+ causesImplicitComponentWrapping(property, childScope));
// unique because it's per-scope and per-property
const auto uniqueBindingId = qMakePair(objectBinding.scope, objectBinding.name);
- const QString typeName = getScopeName(childScope, QQmlJSScope::QMLScope);
+ const QString typeName = getScopeName(childScope, QQmlSA::ScopeType::QMLScope);
if (objectBinding.onToken) {
if (childScope->hasInterface(QStringLiteral("QQmlPropertyValueInterceptor"))) {
@@ -778,7 +861,7 @@ void QQmlJSImportVisitor::checkRequiredProperties()
!= scopesToSearch.constEnd();
if (!found) {
- const QString scopeId = m_scopesById.id(defScope);
+ const QString scopeId = m_scopesById.id(defScope, scope);
bool propertyUsedInRootAlias = false;
if (!scopeId.isEmpty()) {
for (const QQmlJSMetaProperty &property :
@@ -807,14 +890,14 @@ void QQmlJSImportVisitor::checkRequiredProperties()
: QQmlJSScope::ConstPtr();
const QString propertyScopeName = !propertyScope.isNull()
- ? getScopeName(propertyScope, QQmlJSScope::QMLScope)
+ ? getScopeName(propertyScope, QQmlSA::ScopeType::QMLScope)
: u"here"_s;
const QString requiredScopeName = prevRequiredScope
- ? getScopeName(prevRequiredScope, QQmlJSScope::QMLScope)
+ ? getScopeName(prevRequiredScope, QQmlSA::ScopeType::QMLScope)
: u"here"_s;
- std::optional<FixSuggestion> suggestion;
+ std::optional<QQmlJSFixSuggestion> suggestion;
QString message =
QStringLiteral(
@@ -824,15 +907,15 @@ void QQmlJSImportVisitor::checkRequiredProperties()
if (requiredScope != scope) {
if (!prevRequiredScope.isNull()) {
auto sourceScope = prevRequiredScope->baseType();
- suggestion = FixSuggestion {
- { { u"%1:%2:%3: Property marked as required in %4"_s
- .arg(sourceScope->filePath())
- .arg(sourceScope->sourceLocation().startLine)
- .arg(sourceScope->sourceLocation().startColumn)
- .arg(requiredScopeName),
- sourceScope->sourceLocation(), QString(),
- sourceScope->filePath() } }
+ suggestion = QQmlJSFixSuggestion{
+ "%1:%2:%3: Property marked as required in %4."_L1
+ .arg(sourceScope->filePath())
+ .arg(sourceScope->sourceLocation().startLine)
+ .arg(sourceScope->sourceLocation().startColumn)
+ .arg(requiredScopeName),
+ sourceScope->sourceLocation()
};
+ suggestion->setFilename(sourceScope->filePath());
} else {
message += QStringLiteral(" (marked as required by %1)")
.arg(requiredScopeName);
@@ -863,7 +946,7 @@ void QQmlJSImportVisitor::processPropertyBindings()
continue;
// TODO: Can this be in a better suited category?
- std::optional<FixSuggestion> fixSuggestion;
+ std::optional<QQmlJSFixSuggestion> fixSuggestion;
for (QQmlJSScope::ConstPtr baseScope = scope; !baseScope.isNull();
baseScope = baseScope->baseType()) {
@@ -916,12 +999,11 @@ void QQmlJSImportVisitor::checkSignal(
const QQmlJSScope::ConstPtr &signalScope, const QQmlJS::SourceLocation &location,
const QString &handlerName, const QStringList &handlerParameters)
{
- const auto signal = QQmlJSUtils::signalName(handlerName);
+ const auto signal = QQmlSignalNames::handlerNameToSignalName(handlerName);
std::optional<QQmlJSMetaMethod> signalMethod;
- const auto setSignalMethod = [&](const QQmlJSScope::ConstPtr &scope,
- const QString &name) {
- const auto methods = scope->methods(name, QQmlJSMetaMethod::Signal);
+ const auto setSignalMethod = [&](const QQmlJSScope::ConstPtr &scope, const QString &name) {
+ const auto methods = scope->methods(name, QQmlJSMetaMethodType::Signal);
if (!methods.isEmpty())
signalMethod = methods[0];
};
@@ -929,8 +1011,7 @@ void QQmlJSImportVisitor::checkSignal(
if (signal.has_value()) {
if (signalScope->hasMethod(*signal)) {
setSignalMethod(signalScope, *signal);
- } else if (auto p = QQmlJSUtils::changeHandlerProperty(signalScope, *signal);
- p.has_value()) {
+ } else if (auto p = QQmlJSUtils::propertyFromChangedHandler(signalScope, handlerName)) {
// we have a change handler of the form "onXChanged" where 'X'
// is a property name
@@ -945,7 +1026,7 @@ void QQmlJSImportVisitor::checkSignal(
}
if (!signalMethod.has_value()) { // haven't found anything
- std::optional<FixSuggestion> fix;
+ std::optional<QQmlJSFixSuggestion> fix;
// There is a small chance of suggesting this fix for things that are not actually
// QtQml/Connections elements, but rather some other thing that is also called
@@ -957,15 +1038,13 @@ void QQmlJSImportVisitor::checkSignal(
const qsizetype newLength = m_logger->code().indexOf(u'\n', location.end())
- location.offset;
- fix = FixSuggestion { { FixSuggestion::Fix {
- QStringLiteral("Implicitly defining %1 as signal handler in "
- "Connections is deprecated. Create a function "
- "instead")
- .arg(handlerName),
- QQmlJS::SourceLocation(location.offset, newLength, location.startLine,
- location.startColumn),
- QStringLiteral("function %1(%2) { ... }")
- .arg(handlerName, handlerParameters.join(u", ")) } } };
+ fix = QQmlJSFixSuggestion{
+ "Implicitly defining %1 as signal handler in Connections is deprecated. "
+ "Create a function instead."_L1.arg(handlerName),
+ QQmlJS::SourceLocation(location.offset, newLength, location.startLine,
+ location.startColumn),
+ "function %1(%2) { ... }"_L1.arg(handlerName, handlerParameters.join(u", "))
+ };
}
m_logger->log(QStringLiteral("no matching signal found for handler \"%1\"")
@@ -1102,7 +1181,7 @@ void QQmlJSImportVisitor::addDefaultProperties()
// Note: in this specific code path, binding on default property
// means an object binding (we work with pending objects here)
QQmlJSMetaPropertyBinding binding(m_currentScope->sourceLocation(), defaultPropertyName);
- binding.setObject(getScopeName(m_currentScope, QQmlJSScope::QMLScope),
+ binding.setObject(getScopeName(m_currentScope, QQmlSA::ScopeType::QMLScope),
QQmlJSScope::ConstPtr(m_currentScope));
m_bindings.append(UnfinishedBinding { m_currentScope->parentScope(), [=]() { return binding; },
QQmlJSScope::UnnamedPropertyTarget });
@@ -1181,17 +1260,18 @@ void QQmlJSImportVisitor::checkGroupedAndAttachedScopes(QQmlJSScope::ConstPtr sc
auto childScope = children.takeFirst();
const auto type = childScope->scopeType();
switch (type) {
- case QQmlJSScope::GroupedPropertyScope:
- case QQmlJSScope::AttachedPropertyScope:
+ case QQmlSA::ScopeType::GroupedPropertyScope:
+ case QQmlSA::ScopeType::AttachedPropertyScope:
if (!childScope->baseType()) {
m_logger->log(QStringLiteral("unknown %1 property scope %2.")
- .arg(type == QQmlJSScope::GroupedPropertyScope
+ .arg(type == QQmlSA::ScopeType::GroupedPropertyScope
? QStringLiteral("grouped")
: QStringLiteral("attached"),
childScope->internalName()),
qmlUnqualified, childScope->sourceLocation());
}
children.append(childScope->childScopes());
+ break;
default:
break;
}
@@ -1202,9 +1282,9 @@ void QQmlJSImportVisitor::flushPendingSignalParameters()
{
const QQmlJSMetaSignalHandler handler = m_signalHandlers[m_pendingSignalHandler];
for (const QString &parameter : handler.signalParameters) {
- m_currentScope->insertJSIdentifier(
- parameter,
- { QQmlJSScope::JavaScriptIdentifier::Injected, m_pendingSignalHandler, false });
+ m_currentScope->insertJSIdentifier(parameter,
+ { QQmlJSScope::JavaScriptIdentifier::Injected,
+ m_pendingSignalHandler, std::nullopt, false });
}
m_pendingSignalHandler = QQmlJS::SourceLocation();
}
@@ -1270,8 +1350,9 @@ int QQmlJSImportVisitor::synthesizeCompilationUnitRuntimeFunctionIndices(
{
const auto suitableScope = [](const QQmlJSScope::Ptr &scope) {
const auto type = scope->scopeType();
- return type == QQmlJSScope::QMLScope || type == QQmlJSScope::GroupedPropertyScope
- || type == QQmlJSScope::AttachedPropertyScope;
+ return type == QQmlSA::ScopeType::QMLScope
+ || type == QQmlSA::ScopeType::GroupedPropertyScope
+ || type == QQmlSA::ScopeType::AttachedPropertyScope;
};
if (!suitableScope(scope))
@@ -1320,7 +1401,7 @@ void QQmlJSImportVisitor::populateRuntimeFunctionIndicesForDocument() const
bool QQmlJSImportVisitor::visit(QQmlJS::AST::ExpressionStatement *ast)
{
if (m_pendingSignalHandler.isValid()) {
- enterEnvironment(QQmlJSScope::JSFunctionScope, u"signalhandler"_s,
+ enterEnvironment(QQmlSA::ScopeType::JSFunctionScope, u"signalhandler"_s,
ast->firstSourceLocation());
flushPendingSignalParameters();
}
@@ -1329,7 +1410,7 @@ bool QQmlJSImportVisitor::visit(QQmlJS::AST::ExpressionStatement *ast)
void QQmlJSImportVisitor::endVisit(QQmlJS::AST::ExpressionStatement *)
{
- if (m_currentScope->scopeType() == QQmlJSScope::JSFunctionScope
+ if (m_currentScope->scopeType() == QQmlSA::ScopeType::JSFunctionScope
&& m_currentScope->baseTypeName() == u"signalhandler"_s) {
leaveEnvironment();
}
@@ -1366,9 +1447,9 @@ bool QQmlJSImportVisitor::visit(QQmlJS::AST::StringLiteral *sl)
templateString += c;
}
- const FixSuggestion suggestion = { { { u"Use a template literal instead"_s,
- sl->literalToken, u"`" % templateString % u"`",
- QString(), false } } };
+ QQmlJSFixSuggestion suggestion = { "Use a template literal instead."_L1, sl->literalToken,
+ u"`" % templateString % u"`" };
+ suggestion.setAutoApplicable();
m_logger->log(QStringLiteral("String contains unescaped line terminator which is "
"deprecated."),
qmlMultilineStrings, sl->literalToken, true, true, suggestion);
@@ -1381,17 +1462,41 @@ inline QQmlJSImportVisitor::UnfinishedBinding
createNonUniqueScopeBinding(QQmlJSScope::Ptr &scope, const QString &name,
const QQmlJS::SourceLocation &srcLocation);
+static void logLowerCaseImport(QStringView superType, QQmlJS::SourceLocation location,
+ QQmlJSLogger *logger)
+{
+ QStringView namespaceName{ superType };
+ namespaceName = namespaceName.first(namespaceName.indexOf(u'.'));
+ logger->log(u"Namespace '%1' of '%2' must start with an upper case letter."_s.arg(namespaceName)
+ .arg(superType),
+ qmlUncreatableType, location, true, true);
+}
+
bool QQmlJSImportVisitor::visit(UiObjectDefinition *definition)
{
const QString superType = buildName(definition->qualifiedTypeNameId);
const bool isRoot = !rootScopeIsValid();
Q_ASSERT(!superType.isEmpty());
- if (superType.front().isUpper()) {
+
+ // we need to assume that it is a type based on its capitalization. Types defined in inline
+ // components, for example, can have their type definition after their type usages:
+ // Item { property IC myIC; component IC: Item{}; }
+ const qsizetype indexOfTypeName = superType.lastIndexOf(u'.');
+ const bool looksLikeGroupedProperty = superType.front().isLower();
+
+ if (indexOfTypeName != -1 && looksLikeGroupedProperty) {
+ logLowerCaseImport(superType, definition->qualifiedTypeNameId->identifierToken,
+ m_logger);
+ }
+
+ if (!looksLikeGroupedProperty) {
if (!isRoot) {
- enterEnvironment(QQmlJSScope::QMLScope, superType, definition->firstSourceLocation());
+ enterEnvironment(QQmlSA::ScopeType::QMLScope, superType,
+ definition->firstSourceLocation());
} else {
- enterRootScope(QQmlJSScope::QMLScope, superType, definition->firstSourceLocation());
+ enterRootScope(QQmlSA::ScopeType::QMLScope, superType,
+ definition->firstSourceLocation());
m_currentScope->setIsSingleton(m_rootIsSingleton);
}
@@ -1401,7 +1506,6 @@ bool QQmlJSImportVisitor::visit(UiObjectDefinition *definition)
if (isRoot && base->internalName() == u"QQmlComponent") {
m_logger->log(u"Qml top level type cannot be 'Component'."_s, qmlTopLevelComponent,
definition->qualifiedTypeNameId->identifierToken, true, true);
- return false;
}
if (base->isSingleton() && m_currentScope->isComposite()) {
m_logger->log(u"Singleton Type %1 is not creatable."_s.arg(
@@ -1421,19 +1525,19 @@ bool QQmlJSImportVisitor::visit(UiObjectDefinition *definition)
const QString &name = std::get<InlineComponentNameType>(m_currentRootName);
m_currentScope->setIsInlineComponent(true);
m_currentScope->setInlineComponentName(name);
+ m_currentScope->setOwnModuleName(m_exportedRootScope->moduleName());
m_rootScopeImports.setType(name, { m_currentScope, revision });
m_nextIsInlineComponent = false;
}
addDefaultProperties();
- Q_ASSERT(m_currentScope->scopeType() == QQmlJSScope::QMLScope);
+ Q_ASSERT(m_currentScope->scopeType() == QQmlSA::ScopeType::QMLScope);
m_qmlTypes.append(m_currentScope);
m_objectDefinitionScopes << m_currentScope;
} else {
- enterEnvironmentNonUnique(QQmlJSScope::GroupedPropertyScope, superType,
+ enterEnvironmentNonUnique(QQmlSA::ScopeType::GroupedPropertyScope, superType,
definition->firstSourceLocation());
- Q_ASSERT(rootScopeIsValid());
m_bindings.append(createNonUniqueScopeBinding(m_currentScope, superType,
definition->firstSourceLocation()));
QQmlJSScope::resolveTypes(m_currentScope, m_rootScopeImports, &m_usedTypes);
@@ -1484,8 +1588,10 @@ bool QQmlJSImportVisitor::visit(UiPublicMember *publicMember)
}
UiParameterList *param = publicMember->parameters;
QQmlJSMetaMethod method;
- method.setMethodType(QQmlJSMetaMethod::Signal);
+ method.setMethodType(QQmlJSMetaMethodType::Signal);
method.setMethodName(publicMember->name.toString());
+ method.setSourceLocation(combine(publicMember->firstSourceLocation(),
+ publicMember->lastSourceLocation()));
while (param) {
method.addParameter(
QQmlJSMetaParameter(
@@ -1504,6 +1610,10 @@ bool QQmlJSImportVisitor::visit(UiPublicMember *publicMember)
publicMember->firstSourceLocation());
}
QString typeName = buildName(publicMember->memberType);
+ if (typeName.contains(u'.') && typeName.front().isLower()) {
+ logLowerCaseImport(typeName, publicMember->typeToken, m_logger);
+ }
+
QString aliasExpr;
const bool isAlias = (typeName == u"alias"_s);
if (isAlias) {
@@ -1526,9 +1636,11 @@ bool QQmlJSImportVisitor::visit(UiPublicMember *publicMember)
if (const auto idExpression = cast<IdentifierExpression *>(node)) {
aliasExpr.prepend(idExpression->name.toString());
} else {
+ // cast to expression might have failed above, so use publicMember->statement
+ // to obtain the source location
m_logger->log(QStringLiteral("Invalid alias expression. Only IDs and field "
"member expressions can be aliased."),
- qmlSyntax, expression->firstSourceLocation());
+ qmlSyntax, publicMember->statement->firstSourceLocation());
}
};
tryParseAlias();
@@ -1575,7 +1687,7 @@ bool QQmlJSImportVisitor::visit(UiPublicMember *publicMember)
if (parseResult == BindingExpressionParseResult::Script) {
Q_ASSERT(!m_savedBindingOuterScope); // automatically true due to grammar
m_savedBindingOuterScope = m_currentScope;
- enterEnvironment(QQmlJSScope::JSFunctionScope, QStringLiteral("binding"),
+ enterEnvironment(QQmlSA::ScopeType::JSFunctionScope, QStringLiteral("binding"),
publicMember->statement->firstSourceLocation());
}
@@ -1611,9 +1723,11 @@ void QQmlJSImportVisitor::visitFunctionExpressionHelper(QQmlJS::AST::FunctionExp
{
using namespace QQmlJS::AST;
auto name = fexpr->name.toString();
+ bool pending = false;
if (!name.isEmpty()) {
QQmlJSMetaMethod method(name);
- method.setMethodType(QQmlJSMetaMethod::Method);
+ method.setMethodType(QQmlJSMetaMethodType::Method);
+ method.setSourceLocation(combine(fexpr->firstSourceLocation(), fexpr->lastSourceLocation()));
if (!m_pendingMethodAnnotations.isEmpty()) {
method.setAnnotations(m_pendingMethodAnnotations);
@@ -1637,6 +1751,15 @@ void QQmlJSImportVisitor::visitFunctionExpressionHelper(QQmlJS::AST::FunctionExp
} else {
anyFormalTyped = true;
method.addParameter(QQmlJSMetaParameter(parameter.id, type));
+ if (!pending) {
+ m_pendingMethodTypes << PendingMethodType{
+ m_currentScope,
+ name,
+ combine(parameter.typeAnnotation->firstSourceLocation(),
+ parameter.typeAnnotation->lastSourceLocation())
+ };
+ pending = true;
+ }
}
}
}
@@ -1648,9 +1771,17 @@ void QQmlJSImportVisitor::visitFunctionExpressionHelper(QQmlJS::AST::FunctionExp
// Methods with only untyped arguments return an untyped value.
// Methods with at least one typed argument but no explicit return type return void.
// In order to make a function without arguments return void, you have to specify that.
- if (parseTypes && fexpr->typeAnnotation)
+ if (parseTypes && fexpr->typeAnnotation) {
method.setReturnTypeName(fexpr->typeAnnotation->type->toString());
- else if (anyFormalTyped)
+ if (!pending) {
+ m_pendingMethodTypes << PendingMethodType{
+ m_currentScope, name,
+ combine(fexpr->typeAnnotation->firstSourceLocation(),
+ fexpr->typeAnnotation->lastSourceLocation())
+ };
+ pending = true;
+ }
+ } else if (anyFormalTyped)
method.setReturnTypeName(QStringLiteral("void"));
else
method.setReturnTypeName(QStringLiteral("var"));
@@ -1658,15 +1789,16 @@ void QQmlJSImportVisitor::visitFunctionExpressionHelper(QQmlJS::AST::FunctionExp
method.setJsFunctionIndex(addFunctionOrExpression(m_currentScope, method.methodName()));
m_currentScope->addOwnMethod(method);
- if (m_currentScope->scopeType() != QQmlJSScope::QMLScope) {
+ if (m_currentScope->scopeType() != QQmlSA::ScopeType::QMLScope) {
m_currentScope->insertJSIdentifier(name,
{ QQmlJSScope::JavaScriptIdentifier::LexicalScoped,
- fexpr->firstSourceLocation(), false });
+ fexpr->firstSourceLocation(),
+ method.returnTypeName(), false });
}
- enterEnvironment(QQmlJSScope::JSFunctionScope, name, fexpr->firstSourceLocation());
+ enterEnvironment(QQmlSA::ScopeType::JSFunctionScope, name, fexpr->firstSourceLocation());
} else {
addFunctionOrExpression(m_currentScope, QStringLiteral("<anon>"));
- enterEnvironment(QQmlJSScope::JSFunctionScope, QStringLiteral("<anon>"),
+ enterEnvironment(QQmlSA::ScopeType::JSFunctionScope, QStringLiteral("<anon>"),
fexpr->firstSourceLocation());
}
}
@@ -1706,7 +1838,7 @@ bool QQmlJSImportVisitor::visit(QQmlJS::AST::ClassExpression *ast)
QQmlJSMetaProperty prop;
prop.setPropertyName(ast->name.toString());
m_currentScope->addOwnProperty(prop);
- enterEnvironment(QQmlJSScope::JSFunctionScope, ast->name.toString(),
+ enterEnvironment(QQmlSA::ScopeType::JSFunctionScope, ast->name.toString(),
ast->firstSourceLocation());
return true;
}
@@ -1767,13 +1899,18 @@ QQmlJSImportVisitor::parseBindingExpression(const QString &name,
QQmlJSMetaPropertyBinding binding(location, name);
binding.setScriptBinding(addFunctionOrExpression(m_currentScope, name),
- QQmlJSMetaPropertyBinding::Script_PropertyBinding);
- m_bindings.append(UnfinishedBinding { m_currentScope, [=]() { return binding; } });
+ QQmlSA::ScriptBindingKind::PropertyBinding);
+ m_bindings.append(UnfinishedBinding {
+ m_currentScope,
+ [binding = std::move(binding)]() { return binding; }
+ });
return BindingExpressionParseResult::Script;
}
auto expr = exprStatement->expression;
- QQmlJSMetaPropertyBinding binding(expr->firstSourceLocation(), name);
+ QQmlJSMetaPropertyBinding binding(
+ combine(expr->firstSourceLocation(), expr->lastSourceLocation()),
+ name);
bool isUndefinedBinding = false;
@@ -1809,7 +1946,11 @@ QQmlJSImportVisitor::parseBindingExpression(const QString &name,
binding.setStringLiteral(templateLit->value);
} else {
binding.setScriptBinding(addFunctionOrExpression(m_currentScope, name),
- QQmlJSMetaPropertyBinding::Script_PropertyBinding);
+ QQmlSA::ScriptBindingKind::PropertyBinding);
+ for (QQmlJS::AST::TemplateLiteral *l = templateLit; l; l = l->next) {
+ if (QQmlJS::AST::ExpressionNode *expression = l->expression)
+ expression->accept(this);
+ }
}
break;
}
@@ -1827,16 +1968,15 @@ QQmlJSImportVisitor::parseBindingExpression(const QString &name,
if (!binding.isValid()) {
// consider this to be a script binding (see IRBuilder::setBindingValue)
binding.setScriptBinding(addFunctionOrExpression(m_currentScope, name),
- QQmlJSMetaPropertyBinding::Script_PropertyBinding,
- isUndefinedBinding
- ? QQmlJSMetaPropertyBinding::ScriptValue_Undefined
- : QQmlJSMetaPropertyBinding::ScriptValue_Unknown);
+ QQmlSA::ScriptBindingKind::PropertyBinding,
+ isUndefinedBinding ? ScriptBindingValueType::ScriptValue_Undefined
+ : ScriptBindingValueType::ScriptValue_Unknown);
}
m_bindings.append(UnfinishedBinding { m_currentScope, [=]() { return binding; } });
// translations are neither literal bindings nor script bindings
- if (binding.bindingType() == QQmlJSMetaPropertyBinding::Translation
- || binding.bindingType() == QQmlJSMetaPropertyBinding::TranslationById) {
+ if (binding.bindingType() == QQmlSA::BindingType::Translation
+ || binding.bindingType() == QQmlSA::BindingType::TranslationById) {
return BindingExpressionParseResult::Translation;
}
if (!QQmlJSMetaPropertyBinding::isLiteralBinding(binding.bindingType()))
@@ -1872,7 +2012,6 @@ void QQmlJSImportVisitor::handleIdDeclaration(QQmlJS::AST::UiScriptBinding *scri
m_logger->log(u"Failed to parse id"_s, qmlSyntax,
statement->expression->firstSourceLocation());
return QString();
-
}();
if (m_scopesById.existsAnywhereInDocument(name)) {
// ### TODO: find an alternative to breakInhertianceCycles here
@@ -1905,12 +2044,11 @@ createNonUniqueScopeBinding(QQmlJSScope::Ptr &scope, const QString &name,
{
const auto createBinding = [=]() {
const QQmlJSScope::ScopeType type = scope->scopeType();
- Q_ASSERT(type == QQmlJSScope::GroupedPropertyScope
- || type == QQmlJSScope::AttachedPropertyScope);
- const QQmlJSMetaPropertyBinding::BindingType bindingType =
- (type == QQmlJSScope::GroupedPropertyScope)
- ? QQmlJSMetaPropertyBinding::GroupProperty
- : QQmlJSMetaPropertyBinding::AttachedProperty;
+ Q_ASSERT(type == QQmlSA::ScopeType::GroupedPropertyScope
+ || type == QQmlSA::ScopeType::AttachedPropertyScope);
+ const QQmlSA::BindingType bindingType = (type == QQmlSA::ScopeType::GroupedPropertyScope)
+ ? QQmlSA::BindingType::GroupProperty
+ : QQmlSA::BindingType::AttachedProperty;
const auto propertyBindings = scope->parentScope()->ownPropertyBindings(name);
const bool alreadyHasBinding = std::any_of(propertyBindings.first, propertyBindings.second,
@@ -1921,7 +2059,7 @@ createNonUniqueScopeBinding(QQmlJSScope::Ptr &scope, const QString &name,
return QQmlJSMetaPropertyBinding(QQmlJS::SourceLocation {});
QQmlJSMetaPropertyBinding binding(srcLocation, name);
- if (type == QQmlJSScope::GroupedPropertyScope)
+ if (type == QQmlSA::ScopeType::GroupedPropertyScope)
binding.setGroupBinding(static_cast<QSharedPointer<QQmlJSScope>>(scope));
else
binding.setAttachedBinding(static_cast<QSharedPointer<QQmlJSScope>>(scope));
@@ -1957,11 +2095,11 @@ bool QQmlJSImportVisitor::visit(UiScriptBinding *scriptBinding)
const bool isAttachedProperty = name.front().isUpper();
if (isAttachedProperty) {
// attached property
- enterEnvironmentNonUnique(QQmlJSScope::AttachedPropertyScope, prefix + name,
+ enterEnvironmentNonUnique(QQmlSA::ScopeType::AttachedPropertyScope, prefix + name,
group->firstSourceLocation());
} else {
// grouped property
- enterEnvironmentNonUnique(QQmlJSScope::GroupedPropertyScope, prefix + name,
+ enterEnvironmentNonUnique(QQmlSA::ScopeType::GroupedPropertyScope, prefix + name,
group->firstSourceLocation());
}
m_bindings.append(createNonUniqueScopeBinding(m_currentScope, prefix + name,
@@ -1970,11 +2108,11 @@ bool QQmlJSImportVisitor::visit(UiScriptBinding *scriptBinding)
prefix.clear();
}
- auto name = group->name.toString();
+ const auto name = group->name.toString();
// This is a preliminary check.
// Even if the name starts with "on", it might later turn out not to be a signal.
- const auto signal = QQmlJSUtils::signalName(name);
+ const auto signal = QQmlSignalNames::handlerNameToSignalName(name);
if (!signal.has_value() || m_currentScope->hasProperty(name)) {
m_propertyBindings[m_currentScope].append(
@@ -1994,7 +2132,7 @@ bool QQmlJSImportVisitor::visit(UiScriptBinding *scriptBinding)
}
QQmlJSMetaMethod scopeSignal;
- const auto methods = m_currentScope->methods(*signal, QQmlJSMetaMethod::Signal);
+ const auto methods = m_currentScope->methods(*signal, QQmlJSMetaMethodType::Signal);
if (!methods.isEmpty())
scopeSignal = methods[0];
@@ -2019,19 +2157,18 @@ bool QQmlJSImportVisitor::visit(UiScriptBinding *scriptBinding)
signalParameters]() {
// when encountering a signal handler, add it as a script binding
Q_ASSERT(scope->isFullyResolved());
- QQmlJSMetaPropertyBinding::ScriptBindingKind kind =
- QQmlJSMetaPropertyBinding::Script_Invalid;
- const auto methods = scope->methods(signalName, QQmlJSMetaMethod::Signal);
+ QQmlSA::ScriptBindingKind kind = QQmlSA::ScriptBindingKind::Invalid;
+ const auto methods = scope->methods(signalName, QQmlJSMetaMethodType::Signal);
if (!methods.isEmpty()) {
- kind = QQmlJSMetaPropertyBinding::Script_SignalHandler;
+ kind = QQmlSA::ScriptBindingKind::SignalHandler;
checkSignal(scope, groupLocation, name, signalParameters);
- } else if (QQmlJSUtils::changeHandlerProperty(scope, signalName).has_value()) {
- kind = QQmlJSMetaPropertyBinding::Script_ChangeHandler;
+ } else if (QQmlJSUtils::propertyFromChangedHandler(scope, name).has_value()) {
+ kind = QQmlSA::ScriptBindingKind::ChangeHandler;
checkSignal(scope, groupLocation, name, signalParameters);
} else if (scope->hasProperty(name)) {
// Not a signal handler after all.
// We can see this now because the type is fully resolved.
- kind = QQmlJSMetaPropertyBinding::Script_PropertyBinding;
+ kind = QQmlSA::ScriptBindingKind::PropertyBinding;
m_signalHandlers.remove(firstSourceLocation);
} else {
// We already know it's bad, but let's allow checkSignal() to do its thing.
@@ -2049,12 +2186,12 @@ bool QQmlJSImportVisitor::visit(UiScriptBinding *scriptBinding)
// TODO: before leaving the scopes, we must create the binding.
// Leave any group/attached scopes so that the binding scope doesn't see its properties.
- while (m_currentScope->scopeType() == QQmlJSScope::GroupedPropertyScope
- || m_currentScope->scopeType() == QQmlJSScope::AttachedPropertyScope) {
+ while (m_currentScope->scopeType() == QQmlSA::ScopeType::GroupedPropertyScope
+ || m_currentScope->scopeType() == QQmlSA::ScopeType::AttachedPropertyScope) {
leaveEnvironment();
}
- enterEnvironment(QQmlJSScope::JSFunctionScope, QStringLiteral("binding"),
+ enterEnvironment(QQmlSA::ScopeType::JSFunctionScope, QStringLiteral("binding"),
scriptBinding->statement->firstSourceLocation());
return true;
@@ -2079,7 +2216,7 @@ void QQmlJSImportVisitor::endVisit(UiScriptBinding *)
bool QQmlJSImportVisitor::visit(UiArrayBinding *arrayBinding)
{
- enterEnvironment(QQmlJSScope::QMLScope, buildName(arrayBinding->qualifiedId),
+ enterEnvironment(QQmlSA::ScopeType::QMLScope, buildName(arrayBinding->qualifiedId),
arrayBinding->firstSourceLocation());
m_currentScope->setIsArrayScope(true);
@@ -2096,13 +2233,13 @@ void QQmlJSImportVisitor::endVisit(UiArrayBinding *arrayBinding)
// other expressions involving lists (e.g. `var p: [1,2,3]`) are considered
// to be script bindings
const auto children = m_currentScope->childScopes();
- const auto propertyName = getScopeName(m_currentScope, QQmlJSScope::QMLScope);
+ const auto propertyName = getScopeName(m_currentScope, QQmlSA::ScopeType::QMLScope);
leaveEnvironment();
qsizetype i = 0;
for (auto element = arrayBinding->members; element; element = element->next, ++i) {
const auto &type = children[i];
- if ((type->scopeType() != QQmlJSScope::QMLScope)) {
+ if ((type->scopeType() != QQmlSA::ScopeType::QMLScope)) {
m_logger->log(u"Declaring an object which is not an Qml object"
" as a list member."_s, qmlSyntax, element->firstSourceLocation());
return;
@@ -2111,9 +2248,13 @@ void QQmlJSImportVisitor::endVisit(UiArrayBinding *arrayBinding)
<< PendingPropertyObjectBinding { m_currentScope, type, propertyName,
element->firstSourceLocation(), false };
QQmlJSMetaPropertyBinding binding(element->firstSourceLocation(), propertyName);
- binding.setObject(getScopeName(type, QQmlJSScope::QMLScope), QQmlJSScope::ConstPtr(type));
- m_bindings.append(UnfinishedBinding { m_currentScope, [=]() { return binding; },
- QQmlJSScope::ListPropertyTarget });
+ binding.setObject(getScopeName(type, QQmlSA::ScopeType::QMLScope),
+ QQmlJSScope::ConstPtr(type));
+ m_bindings.append(UnfinishedBinding {
+ m_currentScope,
+ [binding = std::move(binding)]() { return binding; },
+ QQmlJSScope::ListPropertyTarget
+ });
}
}
@@ -2124,6 +2265,7 @@ bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiEnumDeclaration *uied)
uied->firstSourceLocation());
}
QQmlJSMetaEnum qmlEnum(uied->name.toString());
+ qmlEnum.setIsQml(true);
for (const auto *member = uied->members; member; member = member->next) {
qmlEnum.addKey(member->member.toString());
qmlEnum.addValue(int(member->value));
@@ -2146,63 +2288,97 @@ void QQmlJSImportVisitor::addImportWithLocation(const QString &name,
m_importLocations.insert(loc);
}
+void QQmlJSImportVisitor::importFromHost(const QString &path, const QString &prefix,
+ const QQmlJS::SourceLocation &location)
+{
+ QFileInfo fileInfo(path);
+ if (!fileInfo.exists()) {
+ m_logger->log("File or directory you are trying to import does not exist: %1."_L1.arg(path),
+ qmlImport, location);
+ return;
+ }
+
+ if (fileInfo.isFile()) {
+ const auto scope = m_importer->importFile(path);
+ const QString actualPrefix = prefix.isEmpty() ? scope->internalName() : prefix;
+ m_rootScopeImports.setType(actualPrefix, { scope, QTypeRevision() });
+ addImportWithLocation(actualPrefix, location);
+ } else if (fileInfo.isDir()) {
+ const auto scopes = m_importer->importDirectory(path, prefix);
+ m_rootScopeImports.addTypes(scopes);
+ for (auto it = scopes.types().keyBegin(), end = scopes.types().keyEnd(); it != end; it++)
+ addImportWithLocation(*it, location);
+ } else {
+ m_logger->log(
+ "%1 is neither a file nor a directory. Are sure the import path is correct?"_L1.arg(
+ path),
+ qmlImport, location);
+ }
+}
+
+void QQmlJSImportVisitor::importFromQrc(const QString &path, const QString &prefix,
+ const QQmlJS::SourceLocation &location)
+{
+ if (const QQmlJSResourceFileMapper *mapper = m_importer->resourceFileMapper()) {
+ if (mapper->isFile(path)) {
+ const auto entry = m_importer->resourceFileMapper()->entry(
+ QQmlJSResourceFileMapper::resourceFileFilter(path));
+ const auto scope = m_importer->importFile(entry.filePath);
+ const QString actualPrefix =
+ prefix.isEmpty() ? QFileInfo(entry.resourcePath).baseName() : prefix;
+ m_rootScopeImports.setType(actualPrefix, { scope, QTypeRevision() });
+ addImportWithLocation(actualPrefix, location);
+ } else {
+ const auto scopes = m_importer->importDirectory(path, prefix);
+ m_rootScopeImports.addTypes(scopes);
+ for (auto it = scopes.types().keyBegin(), end = scopes.types().keyEnd(); it != end; it++)
+ addImportWithLocation(*it, location);
+ }
+ }
+}
+
bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiImport *import)
{
- auto addImportLocation = [this, import](const QString &name) {
- addImportWithLocation(name, import->firstSourceLocation());
- };
// construct path
QString prefix = QLatin1String("");
if (import->asToken.isValid()) {
prefix += import->importId;
+ if (!import->importId.isEmpty() && !import->importId.front().isUpper()) {
+ m_logger->log(u"Import qualifier '%1' must start with a capital letter."_s.arg(
+ import->importId),
+ qmlImport, import->importIdToken, true, true);
+ }
}
- auto filename = import->fileName.toString();
+
+ const QString filename = import->fileName.toString();
if (!filename.isEmpty()) {
- const QFileInfo file(filename);
- const QString absolute = file.isRelative()
- ? QDir::cleanPath(QDir(m_implicitImportDirectory).filePath(filename))
- : filename;
-
- if (absolute.startsWith(u':')) {
- if (m_importer->resourceFileMapper()) {
- if (m_importer->resourceFileMapper()->isFile(absolute.mid(1))) {
- const auto entry = m_importer->resourceFileMapper()->entry(
- QQmlJSResourceFileMapper::resourceFileFilter(absolute.mid(1)));
- const auto scope = m_importer->importFile(entry.filePath);
- const QString actualPrefix = prefix.isEmpty()
- ? QFileInfo(entry.resourcePath).baseName()
- : prefix;
- m_rootScopeImports.setType(actualPrefix, { scope, QTypeRevision() });
-
- addImportLocation(actualPrefix);
- } else {
- const auto scopes = m_importer->importDirectory(absolute, prefix);
- m_rootScopeImports.addTypes(scopes);
- for (auto it = scopes.types().keyBegin(), end = scopes.types().keyEnd(); it != end;
- it++)
- addImportLocation(*it);
- }
+ const QUrl url(filename);
+ const QString scheme = url.scheme();
+ const QQmlJS::SourceLocation importLocation = import->firstSourceLocation();
+ if (scheme == ""_L1) {
+ QFileInfo fileInfo(url.path());
+ QString absolute = fileInfo.isRelative()
+ ? QDir::cleanPath(QDir(m_implicitImportDirectory).filePath(filename))
+ : filename;
+ if (absolute.startsWith(u':')) {
+ importFromQrc(absolute, prefix, importLocation);
+ } else {
+ importFromHost(absolute, prefix, importLocation);
}
-
- processImportWarnings(QStringLiteral("URL \"%1\"").arg(absolute), import->firstSourceLocation());
+ processImportWarnings("path \"%1\""_L1.arg(url.path()), importLocation);
return true;
+ } else if (scheme == "file"_L1) {
+ importFromHost(url.path(), prefix, importLocation);
+ processImportWarnings("URL \"%1\""_L1.arg(url.path()), importLocation);
+ return true;
+ } else if (scheme == "qrc"_L1) {
+ importFromQrc(":"_L1 + url.path(), prefix, importLocation);
+ processImportWarnings("URL \"%1\""_L1.arg(url.path()), importLocation);
+ return true;
+ } else {
+ m_logger->log("Unknown import syntax. Imports can be paths, qrc urls or file urls"_L1,
+ qmlImport, import->firstSourceLocation());
}
-
- QFileInfo path(absolute);
- if (path.isDir()) {
- const auto scopes = m_importer->importDirectory(path.canonicalFilePath(), prefix);
- m_rootScopeImports.addTypes(scopes);
- for (auto it = scopes.types().keyBegin(), end = scopes.types().keyEnd(); it != end; it++)
- addImportLocation(*it);
- } else if (path.isFile()) {
- const auto scope = m_importer->importFile(path.canonicalFilePath());
- const QString actualPrefix = prefix.isEmpty() ? scope->internalName() : prefix;
- m_rootScopeImports.setType(actualPrefix, { scope, QTypeRevision() });
- addImportLocation(actualPrefix);
- }
-
- processImportWarnings(QStringLiteral("path \"%1\"").arg(path.canonicalFilePath()), import->firstSourceLocation());
- return true;
}
const QString path = buildName(import->importUri);
@@ -2214,7 +2390,7 @@ bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiImport *import)
&staticModulesProvided);
m_rootScopeImports.addTypes(imported);
for (auto it = imported.types().keyBegin(), end = imported.types().keyEnd(); it != end; it++)
- addImportLocation(*it);
+ addImportWithLocation(*it, import->firstSourceLocation());
if (prefix.isEmpty()) {
for (const QString &staticModule : staticModulesProvided) {
@@ -2230,6 +2406,21 @@ bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiImport *import)
return true;
}
+#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
+template<typename F>
+void handlePragmaValues(QQmlJS::AST::UiPragma *pragma, F &&assign)
+{
+ for (const QQmlJS::AST::UiPragmaValueList *v = pragma->values; v; v = v->next)
+ assign(v->value);
+}
+#else
+template<typename F>
+void handlePragmaValues(QQmlJS::AST::UiPragma *pragma, F &&assign)
+{
+ assign(pragma->value);
+}
+#endif
+
bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiPragma *pragma)
{
if (pragma->name == u"Strict"_s) {
@@ -2245,37 +2436,43 @@ bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiPragma *pragma)
} else if (pragma->name == u"Singleton") {
m_rootIsSingleton = true;
} else if (pragma->name == u"ComponentBehavior") {
- if (pragma->value == u"Bound") {
- m_scopesById.setComponentsAreBound(true);
- } else if (pragma->value == u"Unbound") {
- m_scopesById.setComponentsAreBound(false);
- } else {
- m_logger->log(
- u"Unkonwn argument \"%s\" to pragma ComponentBehavior"_s.arg(pragma->value),
- qmlSyntax, pragma->firstSourceLocation());
- }
+ handlePragmaValues(pragma, [this, pragma](QStringView value) {
+ if (value == u"Bound") {
+ m_scopesById.setComponentsAreBound(true);
+ } else if (value == u"Unbound") {
+ m_scopesById.setComponentsAreBound(false);
+ } else {
+ m_logger->log(u"Unknown argument \"%1\" to pragma ComponentBehavior"_s.arg(value),
+ qmlSyntax, pragma->firstSourceLocation());
+ }
+ });
} else if (pragma->name == u"FunctionSignatureBehavior") {
- if (pragma->value == u"Enforced") {
- m_scopesById.setSignaturesAreEnforced(true);
- } else if (pragma->value == u"Ignored") {
- m_scopesById.setSignaturesAreEnforced(false);
- } else {
- m_logger->log(
- u"Unkonwn argument \"%s\" to pragma FunctionSignatureBehavior"_s
- .arg(pragma->value),
- qmlSyntax, pragma->firstSourceLocation());
- }
+ handlePragmaValues(pragma, [this, pragma](QStringView value) {
+ if (value == u"Enforced") {
+ m_scopesById.setSignaturesAreEnforced(true);
+ } else if (value == u"Ignored") {
+ m_scopesById.setSignaturesAreEnforced(false);
+ } else {
+ m_logger->log(
+ u"Unknown argument \"%1\" to pragma FunctionSignatureBehavior"_s.arg(value),
+ qmlSyntax, pragma->firstSourceLocation());
+ }
+ });
} else if (pragma->name == u"ValueTypeBehavior") {
- if (pragma->value == u"Copy") {
- m_scopesById.setValueTypesAreCopied(true);
- } else if (pragma->value == u"Reference") {
- m_scopesById.setValueTypesAreCopied(false);
- } else {
- m_logger->log(
- u"Unkonwn argument \"%s\" to pragma ValueTypeBehavior"_s
- .arg(pragma->value),
- qmlSyntax, pragma->firstSourceLocation());
- }
+ handlePragmaValues(pragma, [this, pragma](QStringView value) {
+ if (value == u"Copy") {
+ // Ignore
+ } else if (value == u"Reference") {
+ // Ignore
+ } else if (value == u"Addressable") {
+ m_scopesById.setValueTypesAreAddressable(true);
+ } else if (value == u"Inaddressable") {
+ m_scopesById.setValueTypesAreAddressable(false);
+ } else {
+ m_logger->log(u"Unknown argument \"%1\" to pragma ValueTypeBehavior"_s.arg(value),
+ qmlSyntax, pragma->firstSourceLocation());
+ }
+ });
}
return true;
@@ -2289,7 +2486,7 @@ void QQmlJSImportVisitor::throwRecursionDepthError()
bool QQmlJSImportVisitor::visit(QQmlJS::AST::ClassDeclaration *ast)
{
- enterEnvironment(QQmlJSScope::JSFunctionScope, ast->name.toString(),
+ enterEnvironment(QQmlSA::ScopeType::JSFunctionScope, ast->name.toString(),
ast->firstSourceLocation());
return true;
}
@@ -2301,7 +2498,7 @@ void QQmlJSImportVisitor::endVisit(QQmlJS::AST::ClassDeclaration *)
bool QQmlJSImportVisitor::visit(QQmlJS::AST::ForStatement *ast)
{
- enterEnvironment(QQmlJSScope::JSLexicalScope, QStringLiteral("forloop"),
+ enterEnvironment(QQmlSA::ScopeType::JSLexicalScope, QStringLiteral("forloop"),
ast->firstSourceLocation());
return true;
}
@@ -2313,7 +2510,7 @@ void QQmlJSImportVisitor::endVisit(QQmlJS::AST::ForStatement *)
bool QQmlJSImportVisitor::visit(QQmlJS::AST::ForEachStatement *ast)
{
- enterEnvironment(QQmlJSScope::JSLexicalScope, QStringLiteral("foreachloop"),
+ enterEnvironment(QQmlSA::ScopeType::JSLexicalScope, QStringLiteral("foreachloop"),
ast->firstSourceLocation());
return true;
}
@@ -2325,7 +2522,7 @@ void QQmlJSImportVisitor::endVisit(QQmlJS::AST::ForEachStatement *)
bool QQmlJSImportVisitor::visit(QQmlJS::AST::Block *ast)
{
- enterEnvironment(QQmlJSScope::JSLexicalScope, QStringLiteral("block"),
+ enterEnvironment(QQmlSA::ScopeType::JSLexicalScope, QStringLiteral("block"),
ast->firstSourceLocation());
if (m_pendingSignalHandler.isValid())
@@ -2341,7 +2538,7 @@ void QQmlJSImportVisitor::endVisit(QQmlJS::AST::Block *)
bool QQmlJSImportVisitor::visit(QQmlJS::AST::CaseBlock *ast)
{
- enterEnvironment(QQmlJSScope::JSLexicalScope, QStringLiteral("case"),
+ enterEnvironment(QQmlSA::ScopeType::JSLexicalScope, QStringLiteral("case"),
ast->firstSourceLocation());
return true;
}
@@ -2353,12 +2550,12 @@ void QQmlJSImportVisitor::endVisit(QQmlJS::AST::CaseBlock *)
bool QQmlJSImportVisitor::visit(QQmlJS::AST::Catch *catchStatement)
{
- enterEnvironment(QQmlJSScope::JSLexicalScope, QStringLiteral("catch"),
+ enterEnvironment(QQmlSA::ScopeType::JSLexicalScope, QStringLiteral("catch"),
catchStatement->firstSourceLocation());
m_currentScope->insertJSIdentifier(
catchStatement->patternElement->bindingIdentifier.toString(),
{ QQmlJSScope::JavaScriptIdentifier::LexicalScoped,
- catchStatement->patternElement->firstSourceLocation(),
+ catchStatement->patternElement->firstSourceLocation(), std::nullopt,
catchStatement->patternElement->scope == QQmlJS::AST::VariableScope::Const });
return true;
}
@@ -2370,7 +2567,7 @@ void QQmlJSImportVisitor::endVisit(QQmlJS::AST::Catch *)
bool QQmlJSImportVisitor::visit(QQmlJS::AST::WithStatement *ast)
{
- enterEnvironment(QQmlJSScope::JSLexicalScope, QStringLiteral("with"),
+ enterEnvironment(QQmlSA::ScopeType::JSLexicalScope, QStringLiteral("with"),
ast->firstSourceLocation());
m_logger->log(QStringLiteral("with statements are strongly discouraged in QML "
@@ -2389,12 +2586,17 @@ void QQmlJSImportVisitor::endVisit(QQmlJS::AST::WithStatement *)
bool QQmlJSImportVisitor::visit(QQmlJS::AST::VariableDeclarationList *vdl)
{
while (vdl) {
+ std::optional<QString> typeName;
+ if (TypeAnnotation *annotation = vdl->declaration->typeAnnotation)
+ if (Type *type = annotation->type)
+ typeName = type->toString();
+
m_currentScope->insertJSIdentifier(
vdl->declaration->bindingIdentifier.toString(),
{ (vdl->declaration->scope == QQmlJS::AST::VariableScope::Var)
? QQmlJSScope::JavaScriptIdentifier::FunctionScoped
: QQmlJSScope::JavaScriptIdentifier::LexicalScoped,
- vdl->declaration->firstSourceLocation(),
+ vdl->declaration->firstSourceLocation(), typeName,
vdl->declaration->scope == QQmlJS::AST::VariableScope::Const });
vdl = vdl->next;
}
@@ -2404,9 +2606,14 @@ bool QQmlJSImportVisitor::visit(QQmlJS::AST::VariableDeclarationList *vdl)
bool QQmlJSImportVisitor::visit(QQmlJS::AST::FormalParameterList *fpl)
{
for (auto const &boundName : fpl->boundNames()) {
+
+ std::optional<QString> typeName;
+ if (TypeAnnotation *annotation = boundName.typeAnnotation.data())
+ if (Type *type = annotation->type)
+ typeName = type->toString();
m_currentScope->insertJSIdentifier(boundName.id,
{ QQmlJSScope::JavaScriptIdentifier::Parameter,
- fpl->firstSourceLocation(), false });
+ boundName.location, typeName, false });
}
return true;
}
@@ -2420,6 +2627,11 @@ bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiObjectBinding *uiob)
bool needsResolution = false;
int scopesEnteredCounter = 0;
+ const QString typeName = buildName(uiob->qualifiedTypeNameId);
+ if (typeName.front().isLower() && typeName.contains(u'.')) {
+ logLowerCaseImport(typeName, uiob->qualifiedTypeNameId->identifierToken, m_logger);
+ }
+
QString prefix;
for (auto group = uiob->qualifiedId; group->next; group = group->next) {
const QString idName = group->name.toString();
@@ -2432,8 +2644,8 @@ bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiObjectBinding *uiob)
continue;
}
- const auto scopeKind = idName.front().isUpper() ? QQmlJSScope::AttachedPropertyScope
- : QQmlJSScope::GroupedPropertyScope;
+ const auto scopeKind = idName.front().isUpper() ? QQmlSA::ScopeType::AttachedPropertyScope
+ : QQmlSA::ScopeType::GroupedPropertyScope;
bool exists =
enterEnvironmentNonUnique(scopeKind, prefix + idName, group->firstSourceLocation());
@@ -2455,7 +2667,7 @@ bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiObjectBinding *uiob)
if (needsResolution)
QQmlJSScope::resolveTypes(m_currentScope, m_rootScopeImports, &m_usedTypes);
- enterEnvironment(QQmlJSScope::QMLScope, buildName(uiob->qualifiedTypeNameId),
+ enterEnvironment(QQmlSA::ScopeType::QMLScope, typeName,
uiob->qualifiedTypeNameId->identifierToken);
QQmlJSScope::resolveTypes(m_currentScope, m_rootScopeImports, &m_usedTypes);
@@ -2486,8 +2698,8 @@ void QQmlJSImportVisitor::endVisit(QQmlJS::AST::UiObjectBinding *uiob)
continue;
}
- const auto scopeKind = idName.front().isUpper() ? QQmlJSScope::AttachedPropertyScope
- : QQmlJSScope::GroupedPropertyScope;
+ const auto scopeKind = idName.front().isUpper() ? QQmlSA::ScopeType::AttachedPropertyScope
+ : QQmlSA::ScopeType::GroupedPropertyScope;
// definitely exists
[[maybe_unused]] bool exists =
enterEnvironmentNonUnique(scopeKind, prefix + idName, group->firstSourceLocation());
@@ -2508,7 +2720,7 @@ void QQmlJSImportVisitor::endVisit(QQmlJS::AST::UiObjectBinding *uiob)
while (!childScopes.isEmpty()) {
const QQmlJSScope::ConstPtr scope = childScopes.takeFirst();
- if (!m_scopesById.id(scope).isEmpty()) {
+ if (!m_scopesById.id(scope, scope).isEmpty()) {
foundIds = true;
break;
}
@@ -2535,14 +2747,14 @@ void QQmlJSImportVisitor::endVisit(QQmlJS::AST::UiObjectBinding *uiob)
QQmlJSMetaPropertyBinding binding(uiob->firstSourceLocation(), propertyName);
if (uiob->hasOnToken) {
if (childScope->hasInterface(u"QQmlPropertyValueInterceptor"_s)) {
- binding.setInterceptor(getScopeName(childScope, QQmlJSScope::QMLScope),
+ binding.setInterceptor(getScopeName(childScope, QQmlSA::ScopeType::QMLScope),
QQmlJSScope::ConstPtr(childScope));
} else { // if (childScope->hasInterface(u"QQmlPropertyValueSource"_s))
- binding.setValueSource(getScopeName(childScope, QQmlJSScope::QMLScope),
+ binding.setValueSource(getScopeName(childScope, QQmlSA::ScopeType::QMLScope),
QQmlJSScope::ConstPtr(childScope));
}
} else {
- binding.setObject(getScopeName(childScope, QQmlJSScope::QMLScope),
+ binding.setObject(getScopeName(childScope, QQmlSA::ScopeType::QMLScope),
QQmlJSScope::ConstPtr(childScope));
}
m_bindings.append(UnfinishedBinding { m_currentScope, [=]() { return binding; } });
@@ -2571,8 +2783,8 @@ void QQmlJSImportVisitor::endVisit(ExportDeclaration *)
bool QQmlJSImportVisitor::visit(ESModule *module)
{
Q_ASSERT(!rootScopeIsValid());
- enterRootScope(
- QQmlJSScope::JSLexicalScope, QStringLiteral("module"), module->firstSourceLocation());
+ enterRootScope(QQmlSA::ScopeType::JSLexicalScope, QStringLiteral("module"),
+ module->firstSourceLocation());
m_currentScope->setIsScript(true);
importBaseModules();
leaveEnvironment();
@@ -2602,17 +2814,24 @@ void QQmlJSImportVisitor::endVisit(Program *)
void QQmlJSImportVisitor::endVisit(QQmlJS::AST::FieldMemberExpression *fieldMember)
{
+ // This is a rather rough approximation of "used type" but the "unused import"
+ // info message doesn't have to be 100% accurate.
const QString name = fieldMember->name.toString();
if (m_importTypeLocationMap.contains(name)) {
- if (m_rootScopeImports.isNullType(name))
+ const QQmlJSImportedScope type = m_rootScopeImports.type(name);
+ if (type.scope.isNull()) {
+ if (m_rootScopeImports.hasType(name))
+ m_usedTypes.insert(name);
+ } else if (!type.scope->ownAttachedTypeName().isEmpty()) {
m_usedTypes.insert(name);
+ }
}
}
bool QQmlJSImportVisitor::visit(QQmlJS::AST::IdentifierExpression *idexp)
{
const QString name = idexp->name.toString();
- if (name.front().isUpper() && m_importTypeLocationMap.contains(name)) {
+ if (m_importTypeLocationMap.contains(name)) {
m_usedTypes.insert(name);
}
@@ -2626,12 +2845,16 @@ bool QQmlJSImportVisitor::visit(QQmlJS::AST::PatternElement *element)
QQmlJS::AST::BoundNames names;
element->boundNames(&names);
for (const auto &name : names) {
+ std::optional<QString> typeName;
+ if (TypeAnnotation *annotation = name.typeAnnotation.data())
+ if (Type *type = annotation->type)
+ typeName = type->toString();
m_currentScope->insertJSIdentifier(
name.id,
{ (element->scope == QQmlJS::AST::VariableScope::Var)
? QQmlJSScope::JavaScriptIdentifier::FunctionScoped
: QQmlJSScope::JavaScriptIdentifier::LexicalScoped,
- element->firstSourceLocation(),
+ name.location, typeName,
element->scope == QQmlJS::AST::VariableScope::Const });
}
}
diff --git a/src/qmlcompiler/qqmljsimportvisitor_p.h b/src/qmlcompiler/qqmljsimportvisitor_p.h
index 1a587cbb85..b04b0a0777 100644
--- a/src/qmlcompiler/qqmljsimportvisitor_p.h
+++ b/src/qmlcompiler/qqmljsimportvisitor_p.h
@@ -14,7 +14,8 @@
//
// We mean it.
-#include <private/qtqmlcompilerexports_p.h>
+#include <private/qqmljscontextualtypes_p.h>
+#include <qtqmlcompilerexports.h>
#include "qqmljsannotation_p.h"
#include "qqmljsimporter_p.h"
@@ -33,18 +34,29 @@
QT_BEGIN_NAMESPACE
+namespace QQmlJS::Dom {
+class QQmlDomAstCreatorWithQQmlJSScope;
+}
+
struct QQmlJSResourceFileMapper;
-class Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSImportVisitor : public QQmlJS::AST::Visitor
+class Q_QMLCOMPILER_EXPORT QQmlJSImportVisitor : public QQmlJS::AST::Visitor
{
public:
+ QQmlJSImportVisitor();
QQmlJSImportVisitor(const QQmlJSScope::Ptr &target,
QQmlJSImporter *importer, QQmlJSLogger *logger,
const QString &implicitImportDirectory,
const QStringList &qmldirFiles = QStringList());
~QQmlJSImportVisitor();
+ using QQmlJS::AST::Visitor::endVisit;
+ using QQmlJS::AST::Visitor::postVisit;
+ using QQmlJS::AST::Visitor::preVisit;
+ using QQmlJS::AST::Visitor::visit;
+
QQmlJSScope::Ptr result() const { return m_exportedRootScope; }
+ const QQmlJSLogger *logger() const { return m_logger; }
QQmlJSLogger *logger() { return m_logger; }
QQmlJSImporter::ImportedTypes imports() const { return m_rootScopeImports; }
@@ -63,7 +75,9 @@ public:
static QString implicitImportDirectory(
const QString &localFile, QQmlJSResourceFileMapper *mapper);
- QQmlJSImporter *importer() { return m_importer; } // ### should this be restricted?
+ // ### should this be restricted?
+ QQmlJSImporter *importer() { return m_importer; }
+ const QQmlJSImporter *importer() const { return m_importer; }
struct UnfinishedBinding
{
@@ -246,6 +260,7 @@ protected:
void processPropertyBindings();
void checkRequiredProperties();
void processPropertyTypes();
+ void processMethodTypes();
void processPropertyBindingObjects();
void flushPendingSignalParameters();
@@ -271,6 +286,13 @@ protected:
QQmlJS::SourceLocation location;
};
+ struct PendingMethodType
+ {
+ QQmlJSScope::Ptr scope;
+ QString methodName;
+ QQmlJS::SourceLocation location;
+ };
+
struct PendingPropertyObjectBinding
{
QQmlJSScope::Ptr scope;
@@ -308,6 +330,7 @@ protected:
QHash<QQmlJSScope::Ptr, QVector<QQmlJSScope::Ptr>> m_pendingDefaultProperties;
QVector<PendingPropertyType> m_pendingPropertyTypes;
+ QVector<PendingMethodType> m_pendingMethodTypes;
QVector<PendingPropertyObjectBinding> m_pendingPropertyObjectBindings;
QVector<RequiredProperty> m_requiredProperties;
QVector<QQmlJSScope::Ptr> m_objectBindingScopes;
@@ -336,6 +359,14 @@ private:
const QQmlJS::SourceLocation &location);
void enterRootScope(QQmlJSScope::ScopeType type, const QString &name,
const QQmlJS::SourceLocation &location);
+
+ void importFromHost(const QString &path, const QString &prefix,
+ const QQmlJS::SourceLocation &location);
+ void importFromQrc(const QString &path, const QString &prefix,
+ const QQmlJS::SourceLocation &location);
+
+public:
+ friend class QQmlJS::Dom::QQmlDomAstCreatorWithQQmlJSScope;
};
QT_END_NAMESPACE
diff --git a/src/qmlcompiler/qqmljslinter.cpp b/src/qmlcompiler/qqmljslinter.cpp
index e84b04b713..b5744b0c3d 100644
--- a/src/qmlcompiler/qqmljslinter.cpp
+++ b/src/qmlcompiler/qqmljslinter.cpp
@@ -19,6 +19,7 @@
#include <QtCore/qscopedpointer.h>
#include <QtQmlCompiler/private/qqmlsa_p.h>
+#include <QtQmlCompiler/private/qqmljsloggingutils_p.h>
#if QT_CONFIG(library)
# include <QtCore/qdiriterator.h>
@@ -169,7 +170,7 @@ bool QQmlJSLinter::Plugin::parseMetaData(const QJsonObject &metaData, QString pl
return false;
}
- QJsonObject object = value.toObject();
+ const QJsonObject object = value.toObject();
for (const QString &requiredKey : { u"name"_s, u"description"_s }) {
if (!object.contains(requiredKey)) {
@@ -179,10 +180,18 @@ bool QQmlJSLinter::Plugin::parseMetaData(const QJsonObject &metaData, QString pl
}
}
+ const auto it = object.find("enabled"_L1);
+ const bool ignored = (it != object.end() && !it->toBool());
+
const QString categoryId =
(m_isInternal ? u""_s : u"Plugin."_s) + m_name + u'.' + object[u"name"].toString();
- m_categories << QQmlJSLogger::Category { categoryId, categoryId,
- object[u"description"].toString(), QtWarningMsg };
+ const auto settingsNameIt = object.constFind(u"settingsName");
+ const QString settingsName = (settingsNameIt == object.constEnd())
+ ? categoryId
+ : settingsNameIt->toString(categoryId);
+ m_categories << QQmlJS::LoggerCategory{ categoryId, settingsName,
+ object["description"_L1].toString(), QtWarningMsg,
+ ignored };
}
return true;
@@ -190,7 +199,6 @@ bool QQmlJSLinter::Plugin::parseMetaData(const QJsonObject &metaData, QString pl
std::vector<QQmlJSLinter::Plugin> QQmlJSLinter::loadPlugins(QStringList paths)
{
-
std::vector<Plugin> plugins;
QDuplicateTracker<QString> seenPlugins;
@@ -236,7 +244,7 @@ std::vector<QQmlJSLinter::Plugin> QQmlJSLinter::loadPlugins(QStringList paths)
}
}
#endif
-
+ Q_UNUSED(paths)
return plugins;
}
@@ -265,7 +273,7 @@ void QQmlJSLinter::parseComments(QQmlJSLogger *logger,
const QString category = words.at(i);
const auto categoryExists = std::any_of(
loggerCategories.cbegin(), loggerCategories.cend(),
- [&](const QQmlJSLogger::Category &cat) { return cat.id().name() == category; });
+ [&](const QQmlJS::LoggerCategory &cat) { return cat.id().name() == category; });
if (categoryExists)
categories << category;
@@ -323,7 +331,7 @@ void QQmlJSLinter::parseComments(QQmlJSLogger *logger,
}
static void addJsonWarning(QJsonArray &warnings, const QQmlJS::DiagnosticMessage &message,
- QAnyStringView id, const std::optional<FixSuggestion> &suggestion = {})
+ QAnyStringView id, const std::optional<QQmlJSFixSuggestion> &suggestion = {})
{
QJsonObject jsonMessage;
@@ -362,19 +370,35 @@ static void addJsonWarning(QJsonArray &warnings, const QQmlJS::DiagnosticMessage
jsonMessage[u"message"_s] = message.message;
QJsonArray suggestions;
+ const auto convertLocation = [](const QQmlJS::SourceLocation &source, QJsonObject *target) {
+ target->insert("line"_L1, int(source.startLine));
+ target->insert("column"_L1, int(source.startColumn));
+ target->insert("charOffset"_L1, int(source.offset));
+ target->insert("length"_L1, int(source.length));
+ };
if (suggestion.has_value()) {
- for (const auto &fix : suggestion->fixes) {
- QJsonObject jsonFix;
- jsonFix[u"message"] = fix.message;
- jsonFix[u"line"_s] = static_cast<int>(fix.cutLocation.startLine);
- jsonFix[u"column"_s] = static_cast<int>(fix.cutLocation.startColumn);
- jsonFix[u"charOffset"_s] = static_cast<int>(fix.cutLocation.offset);
- jsonFix[u"length"_s] = static_cast<int>(fix.cutLocation.length);
- jsonFix[u"replacement"_s] = fix.replacementString;
- jsonFix[u"isHint"] = fix.isHint;
- if (!fix.fileName.isEmpty())
- jsonFix[u"fileName"] = fix.fileName;
- suggestions << jsonFix;
+ QJsonObject jsonFix {
+ { "message"_L1, suggestion->fixDescription() },
+ { "replacement"_L1, suggestion->replacement() },
+ { "isHint"_L1, !suggestion->isAutoApplicable() },
+ };
+ convertLocation(suggestion->location(), &jsonFix);
+ const QString filename = suggestion->filename();
+ if (!filename.isEmpty())
+ jsonFix.insert("fileName"_L1, filename);
+ suggestions << jsonFix;
+
+ const QString hint = suggestion->hint();
+ if (!hint.isEmpty()) {
+ // We need to keep compatibility with the JSON format.
+ // Therefore the overly verbose encoding of the hint.
+ QJsonObject jsonHint {
+ { "message"_L1, hint },
+ { "replacement"_L1, QString() },
+ { "isHint"_L1, true }
+ };
+ convertLocation(QQmlJS::SourceLocation(), &jsonHint);
+ suggestions << jsonHint;
}
}
jsonMessage[u"suggestions"] = suggestions;
@@ -398,7 +422,7 @@ QQmlJSLinter::LintResult QQmlJSLinter::lintFile(const QString &filename,
QJsonArray *json, const QStringList &qmlImportPaths,
const QStringList &qmldirFiles,
const QStringList &resourceFiles,
- const QList<QQmlJSLogger::Category> &categories)
+ const QList<QQmlJS::LoggerCategory> &categories)
{
// Make sure that we don't expose an old logger if we return before a new one is created.
m_logger.reset();
@@ -495,17 +519,17 @@ QQmlJSLinter::LintResult QQmlJSLinter::lintFile(const QString &filename,
if (m_enablePlugins) {
for (const Plugin &plugin : m_plugins) {
- for (const QQmlJSLogger::Category &category : plugin.categories())
+ for (const QQmlJS::LoggerCategory &category : plugin.categories())
m_logger->registerCategory(category);
}
}
for (auto it = categories.cbegin(); it != categories.cend(); ++it) {
- if (!it->changed)
+ if (auto logger = *it; !QQmlJS::LoggerCategoryPrivate::get(&logger)->hasChanged())
continue;
- m_logger->setCategoryIgnored(it->id(), it->ignored);
- m_logger->setCategoryLevel(it->id(), it->level);
+ m_logger->setCategoryIgnored(it->id(), it->isIgnored());
+ m_logger->setCategoryLevel(it->id(), it->level());
}
parseComments(m_logger.get(), engine.comments());
@@ -525,25 +549,26 @@ QQmlJSLinter::LintResult QQmlJSLinter::lintFile(const QString &filename,
typeResolver.init(&v, parser.rootNode());
- QQmlJSLiteralBindingCheck literalCheck;
- literalCheck.run(&v, &typeResolver);
-
- QScopedPointer<QQmlSA::PassManager> passMan;
+ using PassManagerPtr = std::unique_ptr<
+ QQmlSA::PassManager, decltype(&QQmlSA::PassManagerPrivate::deletePassManager)>;
+ PassManagerPtr passMan(QQmlSA::PassManagerPrivate::createPassManager(&v, &typeResolver),
+ &QQmlSA::PassManagerPrivate::deletePassManager);
+ passMan->registerPropertyPass(
+ std::make_unique<QQmlJSLiteralBindingCheck>(passMan.get()), QString(),
+ QString(), QString());
if (m_enablePlugins) {
- passMan.reset(new QQmlSA::PassManager(&v, &typeResolver));
-
for (const Plugin &plugin : m_plugins) {
if (!plugin.isValid() || !plugin.isEnabled())
continue;
QQmlSA::LintPlugin *instance = plugin.m_instance;
Q_ASSERT(instance);
- instance->registerPasses(passMan.get(), v.result());
+ instance->registerPasses(passMan.get(),
+ QQmlJSScope::createQQmlSAElement(v.result()));
}
-
- passMan->analyze(v.result());
}
+ passMan->analyze(QQmlJSScope::createQQmlSAElement(v.result()));
success = !m_logger->hasWarnings() && !m_logger->hasErrors();
@@ -553,16 +578,13 @@ QQmlJSLinter::LintResult QQmlJSLinter::lintFile(const QString &filename,
return;
}
- QQmlJSTypeInfo typeInfo;
-
const QStringList resourcePaths = mapper
? mapper->resourcePaths(QQmlJSResourceFileMapper::localFileFilter(filename))
: QStringList();
const QString resolvedPath =
(resourcePaths.size() == 1) ? u':' + resourcePaths.first() : filename;
- QQmlJSLinterCodegen codegen { &m_importer, resolvedPath, qmldirFiles, m_logger.get(),
- &typeInfo };
+ QQmlJSLinterCodegen codegen { &m_importer, resolvedPath, qmldirFiles, m_logger.get() };
codegen.setTypeResolver(std::move(typeResolver));
if (passMan)
codegen.setPassManager(passMan.get());
@@ -603,8 +625,9 @@ QQmlJSLinter::LintResult QQmlJSLinter::lintFile(const QString &filename,
return success ? LintSuccess : HasWarnings;
}
-QQmlJSLinter::LintResult QQmlJSLinter::lintModule(const QString &module, const bool silent,
- QJsonArray *json)
+QQmlJSLinter::LintResult QQmlJSLinter::lintModule(
+ const QString &module, const bool silent, QJsonArray *json,
+ const QStringList &qmlImportPaths, const QStringList &resourceFiles)
{
// Make sure that we don't expose an old logger if we return before a new one is created.
m_logger.reset();
@@ -612,6 +635,15 @@ QQmlJSLinter::LintResult QQmlJSLinter::lintModule(const QString &module, const b
// We can't lint properly if a module has already been pre-cached
m_importer.clearCache();
+ if (m_importer.importPaths() != qmlImportPaths)
+ m_importer.setImportPaths(qmlImportPaths);
+
+ QQmlJSResourceFileMapper mapper(resourceFiles);
+ if (!resourceFiles.isEmpty())
+ m_importer.setResourceFileMapper(&mapper);
+ else
+ m_importer.setResourceFileMapper(nullptr);
+
QJsonArray warnings;
QJsonObject result;
@@ -768,7 +800,7 @@ QQmlJSLinter::FixResult QQmlJSLinter::applyFixes(QString *fixedCode, bool silent
QString code = m_fileContents;
- QList<FixSuggestion::Fix> fixesToApply;
+ QList<QQmlJSFixSuggestion> fixesToApply;
QFileInfo info(m_logger->fileName());
const QString currentFileAbsolutePath = info.absoluteFilePath();
@@ -782,34 +814,33 @@ QQmlJSLinter::FixResult QQmlJSLinter::applyFixes(QString *fixedCode, bool silent
for (const auto &messages : { m_logger->infos(), m_logger->warnings(), m_logger->errors() })
for (const Message &msg : messages) {
- if (!msg.fixSuggestion.has_value())
+ if (!msg.fixSuggestion.has_value() || !msg.fixSuggestion->isAutoApplicable())
continue;
- for (const auto &fix : msg.fixSuggestion->fixes) {
- if (fix.isHint)
- continue;
-
- // Ignore fix suggestions for other files
- if (!fix.fileName.isEmpty()
- && QFileInfo(fix.fileName).absoluteFilePath() != currentFileAbsolutePath) {
- continue;
- }
-
- fixesToApply << fix;
+ // Ignore fix suggestions for other files
+ const QString filename = msg.fixSuggestion->filename();
+ if (!filename.isEmpty()
+ && QFileInfo(filename).absoluteFilePath() != currentFileAbsolutePath) {
+ continue;
}
+
+ fixesToApply << msg.fixSuggestion.value();
}
if (fixesToApply.isEmpty())
return NothingToFix;
std::sort(fixesToApply.begin(), fixesToApply.end(),
- [](FixSuggestion::Fix &a, FixSuggestion::Fix &b) {
- return a.cutLocation.offset < b.cutLocation.offset;
+ [](const QQmlJSFixSuggestion &a, const QQmlJSFixSuggestion &b) {
+ return a.location().offset < b.location().offset;
});
+ const auto dupes = std::unique(fixesToApply.begin(), fixesToApply.end());
+ fixesToApply.erase(dupes, fixesToApply.end());
+
for (auto it = fixesToApply.begin(); it + 1 != fixesToApply.end(); it++) {
- QQmlJS::SourceLocation srcLocA = it->cutLocation;
- QQmlJS::SourceLocation srcLocB = (it + 1)->cutLocation;
+ const QQmlJS::SourceLocation srcLocA = it->location();
+ const QQmlJS::SourceLocation srcLocB = (it + 1)->location();
if (srcLocA.offset + srcLocA.length > srcLocB.offset) {
if (!silent)
qWarning() << "Fixes for two warnings are overlapping, aborting. Please file a bug "
@@ -821,12 +852,14 @@ QQmlJSLinter::FixResult QQmlJSLinter::applyFixes(QString *fixedCode, bool silent
int offsetChange = 0;
for (const auto &fix : fixesToApply) {
- qsizetype cutLocation = fix.cutLocation.offset + offsetChange;
- QString before = code.left(cutLocation);
- QString after = code.mid(cutLocation + fix.cutLocation.length);
-
- code = before + fix.replacementString + after;
- offsetChange += fix.replacementString.size() - fix.cutLocation.length;
+ const QQmlJS::SourceLocation fixLocation = fix.location();
+ qsizetype cutLocation = fixLocation.offset + offsetChange;
+ const QString before = code.left(cutLocation);
+ const QString after = code.mid(cutLocation + fixLocation.length);
+
+ const QString replacement = fix.replacement();
+ code = before + replacement + after;
+ offsetChange += replacement.size() - fixLocation.length;
}
QQmlJS::Engine engine;
diff --git a/src/qmlcompiler/qqmljslinter_p.h b/src/qmlcompiler/qqmljslinter_p.h
index 77a8fe701f..5f367df4c6 100644
--- a/src/qmlcompiler/qqmljslinter_p.h
+++ b/src/qmlcompiler/qqmljslinter_p.h
@@ -14,7 +14,7 @@
//
// We mean it.
-#include <private/qtqmlcompilerexports_p.h>
+#include <qtqmlcompilerexports.h>
#include <QtQmlCompiler/private/qqmljslogger_p.h>
#include <QtQmlCompiler/private/qqmljsimporter_p.h>
@@ -38,7 +38,7 @@ namespace QQmlSA {
class LintPlugin;
}
-class Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSLinter
+class Q_QMLCOMPILER_EXPORT QQmlJSLinter
{
public:
QQmlJSLinter(const QStringList &importPaths,
@@ -48,7 +48,7 @@ public:
enum LintResult { FailedToOpen, FailedToParse, HasWarnings, LintSuccess };
enum FixResult { NothingToFix, FixError, FixSuccess };
- class Q_QMLCOMPILER_PRIVATE_EXPORT Plugin
+ class Q_QMLCOMPILER_EXPORT Plugin
{
Q_DISABLE_COPY(Plugin)
public:
@@ -65,7 +65,7 @@ public:
const QString &description() const { return m_description; }
const QString &version() const { return m_version; }
const QString &author() const { return m_author; }
- const QList<QQmlJSLogger::Category> categories() const
+ const QList<QQmlJS::LoggerCategory> categories() const
{
return m_categories;
}
@@ -95,10 +95,10 @@ public:
QString m_version;
QString m_author;
- QList<QQmlJSLogger::Category> m_categories;
+ QList<QQmlJS::LoggerCategory> m_categories;
QQmlSA::LintPlugin *m_instance;
std::unique_ptr<QPluginLoader> m_loader;
- bool m_isBuiltin;
+ bool m_isBuiltin = false;
bool m_isInternal =
false; // Internal plugins are those developed and maintained inside the Qt project
bool m_isValid = false;
@@ -111,9 +111,10 @@ public:
LintResult lintFile(const QString &filename, const QString *fileContents, const bool silent,
QJsonArray *json, const QStringList &qmlImportPaths,
const QStringList &qmldirFiles, const QStringList &resourceFiles,
- const QList<QQmlJSLogger::Category> &categories);
+ const QList<QQmlJS::LoggerCategory> &categories);
- LintResult lintModule(const QString &uri, const bool silent, QJsonArray *json);
+ LintResult lintModule(const QString &uri, const bool silent, QJsonArray *json,
+ const QStringList &qmlImportPaths, const QStringList &resourceFiles);
FixResult applyFixes(QString *fixedCode, bool silent);
diff --git a/src/qmlcompiler/qqmljslintercodegen.cpp b/src/qmlcompiler/qqmljslintercodegen.cpp
index c7cd5d0d00..60fc9cb5ad 100644
--- a/src/qmlcompiler/qqmljslintercodegen.cpp
+++ b/src/qmlcompiler/qqmljslintercodegen.cpp
@@ -6,6 +6,7 @@
#include <QtQmlCompiler/private/qqmljsimportvisitor_p.h>
#include <QtQmlCompiler/private/qqmljsshadowcheck_p.h>
#include <QtQmlCompiler/private/qqmljsstoragegeneralizer_p.h>
+#include <QtQmlCompiler/private/qqmljsstorageinitializer_p.h>
#include <QtQmlCompiler/private/qqmljstypepropagator_p.h>
#include <QtQmlCompiler/private/qqmljsfunctioninitializer_p.h>
@@ -16,9 +17,8 @@ QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
QQmlJSLinterCodegen::QQmlJSLinterCodegen(QQmlJSImporter *importer, const QString &fileName,
- const QStringList &qmldirFiles, QQmlJSLogger *logger,
- QQmlJSTypeInfo *typeInfo)
- : QQmlJSAotCompiler(importer, fileName, qmldirFiles, logger), m_typeInfo(typeInfo)
+ const QStringList &qmldirFiles, QQmlJSLogger *logger)
+ : QQmlJSAotCompiler(importer, fileName, qmldirFiles, logger)
{
}
@@ -28,7 +28,6 @@ void QQmlJSLinterCodegen::setDocument(const QmlIR::JSCodeGen *codegen,
Q_UNUSED(codegen);
m_document = document;
m_unitGenerator = &document->jsGenerator;
- m_entireSourceCodeLines = document->code.split(u'\n');
}
std::variant<QQmlJSAotFunction, QQmlJS::DiagnosticMessage>
@@ -84,17 +83,25 @@ bool QQmlJSLinterCodegen::analyzeFunction(const QV4::Compiler::Context *context,
QQmlJSCompilePass::Function *function,
QQmlJS::DiagnosticMessage *error)
{
- QQmlJSTypePropagator propagator(m_unitGenerator, &m_typeResolver, m_logger, m_typeInfo,
- m_passManager);
- QQmlJSCompilePass::InstructionAnnotations annotations = propagator.run(function, error);
+ QQmlJSTypePropagator propagator(m_unitGenerator, &m_typeResolver, m_logger,
+ {}, {}, m_passManager);
+ auto [basicBlocks, annotations] = propagator.run(function, error);
if (!error->isValid()) {
- QQmlJSShadowCheck shadowCheck(m_unitGenerator, &m_typeResolver, m_logger);
- shadowCheck.run(&annotations, function, error);
+ QQmlJSShadowCheck shadowCheck(m_unitGenerator, &m_typeResolver, m_logger, basicBlocks,
+ annotations);
+ shadowCheck.run(function, error);
}
if (!error->isValid()) {
- QQmlJSStorageGeneralizer generalizer(m_unitGenerator, &m_typeResolver, m_logger);
- generalizer.run(annotations, function, error);
+ QQmlJSStorageInitializer initializer(m_unitGenerator, &m_typeResolver, m_logger,
+ basicBlocks, annotations);
+ initializer.run(function, error);
+ }
+
+ if (!error->isValid()) {
+ QQmlJSStorageGeneralizer generalizer(m_unitGenerator, &m_typeResolver, m_logger,
+ basicBlocks, annotations);
+ generalizer.run(function, error);
}
if (error->isValid()) {
diff --git a/src/qmlcompiler/qqmljslintercodegen_p.h b/src/qmlcompiler/qqmljslintercodegen_p.h
index 3a095f0091..5cddbea704 100644
--- a/src/qmlcompiler/qqmljslintercodegen_p.h
+++ b/src/qmlcompiler/qqmljslintercodegen_p.h
@@ -39,8 +39,7 @@ class QQmlJSLinterCodegen : public QQmlJSAotCompiler
{
public:
QQmlJSLinterCodegen(QQmlJSImporter *importer, const QString &fileName,
- const QStringList &qmldirFiles, QQmlJSLogger *logger,
- QQmlJSTypeInfo *typeInfo);
+ const QStringList &qmldirFiles, QQmlJSLogger *logger);
void setDocument(const QmlIR::JSCodeGen *codegen, const QmlIR::Document *document) override;
std::variant<QQmlJSAotFunction, QQmlJS::DiagnosticMessage>
@@ -62,7 +61,6 @@ public:
QQmlSA::PassManager *passManager() { return m_passManager; }
private:
- QQmlJSTypeInfo *m_typeInfo;
QQmlSA::PassManager *m_passManager = nullptr;
bool analyzeFunction(const QV4::Compiler::Context *context,
diff --git a/src/qmlcompiler/qqmljsliteralbindingcheck.cpp b/src/qmlcompiler/qqmljsliteralbindingcheck.cpp
index a2d5222b2b..af1fb9104f 100644
--- a/src/qmlcompiler/qqmljsliteralbindingcheck.cpp
+++ b/src/qmlcompiler/qqmljsliteralbindingcheck.cpp
@@ -6,15 +6,22 @@
#include <private/qqmljsimportvisitor_p.h>
#include <private/qqmljstyperesolver_p.h>
#include <private/qqmljsmetatypes_p.h>
+#include <private/qqmlsa_p.h>
+#include <private/qqmlsasourcelocation_p.h>
+#include <private/qqmlstringconverters_p.h>
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
// This makes no sense, but we want to warn about things QQmlPropertyResolver complains about.
-static bool canConvertForLiteralBinding(
- QQmlJSTypeResolver *resolver, const QQmlJSScope::ConstPtr &from,
- const QQmlJSScope::ConstPtr &to) {
+static bool canConvertForLiteralBinding(QQmlJSTypeResolver *resolver,
+ const QQmlSA::Element &fromElement,
+ const QQmlSA::Element &toElement)
+{
+ Q_ASSERT(resolver);
+ auto from = QQmlJSScope::scope(fromElement);
+ auto to = QQmlJSScope::scope(toElement);
if (resolver->equals(from, to))
return true;
@@ -39,39 +46,113 @@ static bool canConvertForLiteralBinding(
return true;
}
-void QQmlJSLiteralBindingCheck::run(QQmlJSImportVisitor *visitor, QQmlJSTypeResolver *resolver)
+QQmlJSLiteralBindingCheck::QQmlJSLiteralBindingCheck(QQmlSA::PassManager *passManager)
+ : LiteralBindingCheckBase(passManager),
+ m_resolver(QQmlSA::PassManagerPrivate::resolver(*passManager))
{
- QQmlJSLogger *logger = visitor->logger();
- const auto literalScopes = visitor->literalScopesToCheck();
- for (const auto &scope : literalScopes) {
- const auto bindings = scope->ownPropertyBindings();
- for (const auto &binding : bindings) {
- if (!binding.hasLiteral())
- continue;
-
- const QString propertyName = binding.propertyName();
- const QQmlJSMetaProperty property = scope->property(propertyName);
- if (!property.isValid())
- continue;
-
- // If the property is defined in the same scope where it is set,
- // we are in fact allowed to set it, even if it's not writable.
- if (!property.isWritable() && !scope->hasOwnProperty(propertyName)) {
- logger->log(u"Cannot assign to read-only property %1"_s.arg(propertyName),
- qmlReadOnlyProperty, binding.sourceLocation());
- continue;
- }
+}
+
+QQmlJSStructuredTypeError QQmlJSLiteralBindingCheck::check(const QString &typeName,
+ const QString &value) const
+{
+ return QQmlJSValueTypeFromStringCheck::hasError(typeName, value);
+}
+
+static QString literalPrettyTypeName(QQmlSA::BindingType type)
+{
+ switch (type) {
+ case QQmlSA::BindingType::BoolLiteral:
+ return u"bool"_s;
+ case QQmlSA::BindingType::NumberLiteral:
+ return u"double"_s;
+ case QQmlSA::BindingType::StringLiteral:
+ return u"string"_s;
+ case QQmlSA::BindingType::RegExpLiteral:
+ return u"regexp"_s;
+ case QQmlSA::BindingType::Null:
+ return u"null"_s;
+ default:
+ return QString();
+ }
+ Q_UNREACHABLE_RETURN(QString());
+}
+
+QQmlSA::Property LiteralBindingCheckBase::getProperty(const QString &propertyName,
+ const QQmlSA::Binding &binding,
+ const QQmlSA::Element &bindingScope) const
+{
+ if (!QQmlSA::Binding::isLiteralBinding(binding.bindingType()))
+ return {};
- if (!canConvertForLiteralBinding(
- resolver, binding.literalType(resolver), property.type())) {
- logger->log(u"Cannot assign literal of type %1 to %2"_s.arg(
- QQmlJSScope::prettyName(binding.literalTypeName()),
- QQmlJSScope::prettyName(property.typeName())),
- qmlIncompatibleType, binding.sourceLocation());
- continue;
+ const QString unqualifiedPropertyName = [&propertyName]() -> QString {
+ if (auto idx = propertyName.lastIndexOf(u'.'); idx != -1 && idx != propertyName.size() - 1)
+ return propertyName.sliced(idx + 1);
+ return propertyName;
+ }();
+
+ return bindingScope.property(unqualifiedPropertyName);
+}
+
+
+void LiteralBindingCheckBase::onBinding(const QQmlSA::Element &element, const QString &propertyName,
+ const QQmlSA::Binding &binding,
+ const QQmlSA::Element &bindingScope,
+ const QQmlSA::Element &value)
+{
+ Q_UNUSED(value);
+
+ const auto property = getProperty(propertyName, binding, bindingScope);
+ if (!property.isValid())
+ return;
+
+ // If the property is defined in the same scope where it is set,
+ // we are in fact allowed to set it, even if it's not writable.
+ if (property.isReadonly() && !element.hasOwnProperty(propertyName)) {
+ emitWarning(u"Cannot assign to read-only property %1"_s.arg(propertyName),
+ qmlReadOnlyProperty, binding.sourceLocation());
+ return;
+ }
+ if (auto propertyType = property.type(); propertyType) {
+ auto construction = check(propertyType.internalId(), binding.stringValue());
+ if (construction.isValid()) {
+ const QString warningMessage =
+ u"Binding is not supported: Type %1 should be constructed using"
+ u" QML_STRUCTURED_VALUE's construction mechanism, instead of a "
+ u"string."_s.arg(propertyType.internalId());
+
+ if (!construction.code.isNull()) {
+ QQmlSA::FixSuggestion suggestion(
+ u"Replace string by structured value construction"_s,
+ binding.sourceLocation(), construction.code);
+ emitWarning(warningMessage, qmlIncompatibleType, binding.sourceLocation(), suggestion);
+ return;
}
+ emitWarning(warningMessage, qmlIncompatibleType, binding.sourceLocation());
+ return;
}
}
+
+}
+
+void QQmlJSLiteralBindingCheck::onBinding(const QQmlSA::Element &element,
+ const QString &propertyName,
+ const QQmlSA::Binding &binding,
+ const QQmlSA::Element &bindingScope,
+ const QQmlSA::Element &value)
+{
+ LiteralBindingCheckBase::onBinding(element, propertyName, binding, bindingScope, value);
+
+ const auto property = getProperty(propertyName, binding, bindingScope);
+ if (!property.isValid())
+ return;
+
+ if (!canConvertForLiteralBinding(m_resolver, resolveLiteralType(binding), property.type())) {
+ emitWarning(u"Cannot assign literal of type %1 to %2"_s.arg(
+ literalPrettyTypeName(binding.bindingType()),
+ QQmlJSScope::prettyName(property.typeName())),
+ qmlIncompatibleType, binding.sourceLocation());
+ return;
+ }
}
QT_END_NAMESPACE
diff --git a/src/qmlcompiler/qqmljsliteralbindingcheck_p.h b/src/qmlcompiler/qqmljsliteralbindingcheck_p.h
index 92acd0555c..ca5c65e9b4 100644
--- a/src/qmlcompiler/qqmljsliteralbindingcheck_p.h
+++ b/src/qmlcompiler/qqmljsliteralbindingcheck_p.h
@@ -15,17 +15,46 @@
// We mean it.
#include <QtCore/qglobal.h>
-#include <private/qtqmlcompilerexports_p.h>
+#include <QtQmlCompiler/qqmlsa.h>
+
+#include <qtqmlcompilerexports.h>
+#include "qqmljsvaluetypefromstringcheck_p.h"
QT_BEGIN_NAMESPACE
class QQmlJSImportVisitor;
class QQmlJSTypeResolver;
-class Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSLiteralBindingCheck
+class Q_QMLCOMPILER_EXPORT LiteralBindingCheckBase : public QQmlSA::PropertyPass
{
public:
- void run(QQmlJSImportVisitor *visitor, QQmlJSTypeResolver *resolver);
+ using QQmlSA::PropertyPass::PropertyPass;
+
+ void onBinding(const QQmlSA::Element &element, const QString &propertyName,
+ const QQmlSA::Binding &binding, const QQmlSA::Element &bindingScope,
+ const QQmlSA::Element &value) override;
+
+protected:
+ virtual QQmlJSStructuredTypeError check(const QString &typeName, const QString &value) const = 0;
+
+ QQmlSA::Property getProperty(const QString &propertyName, const QQmlSA::Binding &binding,
+ const QQmlSA::Element &bindingScope) const;
+};
+
+class Q_QMLCOMPILER_EXPORT QQmlJSLiteralBindingCheck: public LiteralBindingCheckBase
+{
+public:
+ QQmlJSLiteralBindingCheck(QQmlSA::PassManager *manager);
+
+ void onBinding(const QQmlSA::Element &element, const QString &propertyName,
+ const QQmlSA::Binding &binding, const QQmlSA::Element &bindingScope,
+ const QQmlSA::Element &value) override;
+
+private:
+ QQmlJSTypeResolver *m_resolver;
+
+protected:
+ QQmlJSStructuredTypeError check(const QString &typeName, const QString &value) const override;
};
QT_END_NAMESPACE
diff --git a/src/qmlcompiler/qqmljsloadergenerator.cpp b/src/qmlcompiler/qqmljsloadergenerator.cpp
index 6e9fabbf60..494ecdadd0 100644
--- a/src/qmlcompiler/qqmljsloadergenerator.cpp
+++ b/src/qmlcompiler/qqmljsloadergenerator.cpp
@@ -110,7 +110,7 @@ bool qQmlJSGenerateLoader(const QStringList &compiledFiles, const QString &outpu
const QString ns = qQmlJSSymbolNamespaceForPath(compiledFile);
stream << "namespace " << ns << " { \n";
stream << " extern const unsigned char qmlData[];\n";
- stream << " extern const QQmlPrivate::TypedFunction aotBuiltFunctions[];\n";
+ stream << " extern const QQmlPrivate::AOTCompiledFunction aotBuiltFunctions[];\n";
stream << " const QQmlPrivate::CachedQmlUnit unit = {\n";
stream << " reinterpret_cast<const QV4::CompiledData::Unit*>(&qmlData), &aotBuiltFunctions[0], nullptr\n";
stream << " };\n";
diff --git a/src/qmlcompiler/qqmljsloadergenerator_p.h b/src/qmlcompiler/qqmljsloadergenerator_p.h
index fc207f075b..f76fdef17b 100644
--- a/src/qmlcompiler/qqmljsloadergenerator_p.h
+++ b/src/qmlcompiler/qqmljsloadergenerator_p.h
@@ -14,7 +14,7 @@
//
// We mean it.
-#include <private/qtqmlcompilerexports_p.h>
+#include <qtqmlcompilerexports.h>
#include <QtCore/qstring.h>
#include <QtCore/qlist.h>
@@ -22,11 +22,11 @@
QT_BEGIN_NAMESPACE
-bool Q_QMLCOMPILER_PRIVATE_EXPORT qQmlJSGenerateLoader(const QStringList &compiledFiles,
+bool Q_QMLCOMPILER_EXPORT qQmlJSGenerateLoader(const QStringList &compiledFiles,
const QString &outputFileName,
const QStringList &resourceFileMappings,
QString *errorString);
-QString Q_QMLCOMPILER_PRIVATE_EXPORT qQmlJSSymbolNamespaceForPath(const QString &relativePath);
+QString Q_QMLCOMPILER_EXPORT qQmlJSSymbolNamespaceForPath(const QString &relativePath);
QT_END_NAMESPACE
diff --git a/src/qmlcompiler/qqmljslogger.cpp b/src/qmlcompiler/qqmljslogger.cpp
index 293069a2b6..7bf686018d 100644
--- a/src/qmlcompiler/qqmljslogger.cpp
+++ b/src/qmlcompiler/qqmljslogger.cpp
@@ -1,210 +1,225 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-#include <qglobal.h>
-
+#include <QtCore/qcompilerdetection.h>
// GCC 11 thinks diagMsg.fixSuggestion.fixes.d.ptr is somehow uninitialized in
// QList::emplaceBack(), probably called from QQmlJsLogger::log()
// Ditto for GCC 12, but it emits a different warning
QT_WARNING_PUSH
QT_WARNING_DISABLE_GCC("-Wuninitialized")
QT_WARNING_DISABLE_GCC("-Wmaybe-uninitialized")
-#include <qlist.h>
+#include <QtCore/qlist.h>
QT_WARNING_POP
-#include "qqmljslogger_p.h"
+#include <private/qqmljslogger_p.h>
+#include <private/qqmlsa_p.h>
+
+#include <QtQmlCompiler/qqmljsloggingutils.h>
+#include <QtCore/qglobal.h>
#include <QtCore/qfile.h>
#include <QtCore/qfileinfo.h>
+
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
-const LoggerWarningId qmlRequired { "required" };
-const LoggerWarningId qmlAliasCycle { "alias-cycle" };
-const LoggerWarningId qmlUnresolvedAlias { "unresolved-alias" };
-const LoggerWarningId qmlImport { "import" };
-const LoggerWarningId qmlRecursionDepthErrors { "recursion-depth-errors" };
-const LoggerWarningId qmlWith { "with" };
-const LoggerWarningId qmlInheritanceCycle { "inheritance-cycle" };
-const LoggerWarningId qmlDeprecated { "deprecated" };
-const LoggerWarningId qmlSignalParameters { "signal-handler-parameters" };
-const LoggerWarningId qmlMissingType { "missing-type" };
-const LoggerWarningId qmlUnresolvedType { "unresolved-type" };
-const LoggerWarningId qmlRestrictedType { "restricted-type" };
-const LoggerWarningId qmlPrefixedImportType { "prefixed-import-type" };
-const LoggerWarningId qmlIncompatibleType { "incompatible-type" };
-const LoggerWarningId qmlMissingProperty { "missing-property" };
-const LoggerWarningId qmlNonListProperty { "non-list-property" };
-const LoggerWarningId qmlReadOnlyProperty { "read-only-property" };
-const LoggerWarningId qmlDuplicatePropertyBinding { "duplicate-property-binding" };
-const LoggerWarningId qmlDuplicatedName { "duplicated-name" };
-const LoggerWarningId qmlDeferredPropertyId { "deferred-property-id" };
-const LoggerWarningId qmlUnqualified { "unqualified" };
-const LoggerWarningId qmlUnusedImports { "unused-imports" };
-const LoggerWarningId qmlMultilineStrings { "multiline-strings" };
-const LoggerWarningId qmlSyntax { "syntax" };
-const LoggerWarningId qmlSyntaxIdQuotation { "syntax.id-quotation" };
-const LoggerWarningId qmlSyntaxDuplicateIds { "syntax.duplicate-ids" };
-const LoggerWarningId qmlCompiler { "compiler" };
-const LoggerWarningId qmlAttachedPropertyReuse { "attached-property-reuse" };
-const LoggerWarningId qmlPlugin { "plugin" };
-const LoggerWarningId qmlVarUsedBeforeDeclaration { "var-used-before-declaration" };
-const LoggerWarningId qmlInvalidLintDirective { "invalid-lint-directive" };
-const LoggerWarningId qmlUseProperFunction { "use-proper-function" };
-const LoggerWarningId qmlAccessSingleton { "access-singleton-via-object" };
-const LoggerWarningId qmlTopLevelComponent { "top-level-component" };
-const LoggerWarningId qmlUncreatableType { "uncreatable-type" };
-
-
-const QList<QQmlJSLogger::Category> &QQmlJSLogger::defaultCategories()
+const QQmlSA::LoggerWarningId qmlRequired{ "required" };
+const QQmlSA::LoggerWarningId qmlAliasCycle{ "alias-cycle" };
+const QQmlSA::LoggerWarningId qmlUnresolvedAlias{ "unresolved-alias" };
+const QQmlSA::LoggerWarningId qmlImport{ "import" };
+const QQmlSA::LoggerWarningId qmlRecursionDepthErrors{ "recursion-depth-errors" };
+const QQmlSA::LoggerWarningId qmlWith{ "with" };
+const QQmlSA::LoggerWarningId qmlInheritanceCycle{ "inheritance-cycle" };
+const QQmlSA::LoggerWarningId qmlDeprecated{ "deprecated" };
+const QQmlSA::LoggerWarningId qmlSignalParameters{ "signal-handler-parameters" };
+const QQmlSA::LoggerWarningId qmlMissingType{ "missing-type" };
+const QQmlSA::LoggerWarningId qmlUnresolvedType{ "unresolved-type" };
+const QQmlSA::LoggerWarningId qmlRestrictedType{ "restricted-type" };
+const QQmlSA::LoggerWarningId qmlPrefixedImportType{ "prefixed-import-type" };
+const QQmlSA::LoggerWarningId qmlIncompatibleType{ "incompatible-type" };
+const QQmlSA::LoggerWarningId qmlMissingProperty{ "missing-property" };
+const QQmlSA::LoggerWarningId qmlNonListProperty{ "non-list-property" };
+const QQmlSA::LoggerWarningId qmlReadOnlyProperty{ "read-only-property" };
+const QQmlSA::LoggerWarningId qmlDuplicatePropertyBinding{ "duplicate-property-binding" };
+const QQmlSA::LoggerWarningId qmlDuplicatedName{ "duplicated-name" };
+const QQmlSA::LoggerWarningId qmlDeferredPropertyId{ "deferred-property-id" };
+const QQmlSA::LoggerWarningId qmlUnqualified{ "unqualified" };
+const QQmlSA::LoggerWarningId qmlUnusedImports{ "unused-imports" };
+const QQmlSA::LoggerWarningId qmlMultilineStrings{ "multiline-strings" };
+const QQmlSA::LoggerWarningId qmlSyntax{ "syntax" };
+const QQmlSA::LoggerWarningId qmlSyntaxIdQuotation{ "syntax.id-quotation" };
+const QQmlSA::LoggerWarningId qmlSyntaxDuplicateIds{ "syntax.duplicate-ids" };
+const QQmlSA::LoggerWarningId qmlCompiler{ "compiler" };
+const QQmlSA::LoggerWarningId qmlAttachedPropertyReuse{ "attached-property-reuse" };
+const QQmlSA::LoggerWarningId qmlPlugin{ "plugin" };
+const QQmlSA::LoggerWarningId qmlVarUsedBeforeDeclaration{ "var-used-before-declaration" };
+const QQmlSA::LoggerWarningId qmlInvalidLintDirective{ "invalid-lint-directive" };
+const QQmlSA::LoggerWarningId qmlUseProperFunction{ "use-proper-function" };
+const QQmlSA::LoggerWarningId qmlAccessSingleton{ "access-singleton-via-object" };
+const QQmlSA::LoggerWarningId qmlTopLevelComponent{ "top-level-component" };
+const QQmlSA::LoggerWarningId qmlUncreatableType{ "uncreatable-type" };
+const QQmlSA::LoggerWarningId qmlMissingEnumEntry{ "missing-enum-entry" };
+
+QQmlJSLogger::QQmlJSLogger()
{
- static const QList<QQmlJSLogger::Category> cats = {
- QQmlJSLogger::Category { qmlRequired.name().toString(), QStringLiteral("RequiredProperty"),
- QStringLiteral("Warn about required properties"), QtWarningMsg },
- QQmlJSLogger::Category { qmlAliasCycle.name().toString(),
- QStringLiteral("PropertyAliasCycles"),
- QStringLiteral("Warn about alias cycles"), QtWarningMsg },
- QQmlJSLogger::Category { qmlUnresolvedAlias.name().toString(),
- QStringLiteral("PropertyAliasCycles"),
- QStringLiteral("Warn about unresolved aliases"), QtWarningMsg },
- QQmlJSLogger::Category {
+ static const QList<QQmlJS::LoggerCategory> cats = defaultCategories();
+
+ for (const QQmlJS::LoggerCategory &category : cats)
+ registerCategory(category);
+
+ // setup color output
+ m_output.insertMapping(QtCriticalMsg, QColorOutput::RedForeground);
+ m_output.insertMapping(QtWarningMsg, QColorOutput::PurpleForeground); // Yellow?
+ m_output.insertMapping(QtInfoMsg, QColorOutput::BlueForeground);
+ m_output.insertMapping(QtDebugMsg, QColorOutput::GreenForeground); // None?
+}
+
+const QList<QQmlJS::LoggerCategory> &QQmlJSLogger::defaultCategories()
+{
+ static const QList<QQmlJS::LoggerCategory> cats = {
+ QQmlJS::LoggerCategory{ qmlRequired.name().toString(), QStringLiteral("RequiredProperty"),
+ QStringLiteral("Warn about required properties"), QtWarningMsg },
+ QQmlJS::LoggerCategory{ qmlAliasCycle.name().toString(),
+ QStringLiteral("PropertyAliasCycles"),
+ QStringLiteral("Warn about alias cycles"), QtWarningMsg },
+ QQmlJS::LoggerCategory{ qmlUnresolvedAlias.name().toString(),
+ QStringLiteral("PropertyAliasCycles"),
+ QStringLiteral("Warn about unresolved aliases"), QtWarningMsg },
+ QQmlJS::LoggerCategory{
qmlImport.name().toString(), QStringLiteral("ImportFailure"),
QStringLiteral("Warn about failing imports and deprecated qmltypes"),
QtWarningMsg },
- QQmlJSLogger::Category {
+ QQmlJS::LoggerCategory{
qmlRecursionDepthErrors.name().toString(), QStringLiteral("ImportFailure"),
QStringLiteral("Warn about failing imports and deprecated qmltypes"), QtWarningMsg,
false, true },
- QQmlJSLogger::Category {
- qmlWith.name().toString(), QStringLiteral("WithStatement"),
- QStringLiteral("Warn about with statements as they can cause false "
- "positives when checking for unqualified access"),
- QtWarningMsg },
- QQmlJSLogger::Category { qmlInheritanceCycle.name().toString(),
- QStringLiteral("InheritanceCycle"),
- QStringLiteral("Warn about inheritance cycles"), QtWarningMsg },
- QQmlJSLogger::Category { qmlDeprecated.name().toString(), QStringLiteral("Deprecated"),
- QStringLiteral("Warn about deprecated properties and types"),
- QtWarningMsg },
- QQmlJSLogger::Category {
+ QQmlJS::LoggerCategory{ qmlWith.name().toString(), QStringLiteral("WithStatement"),
+ QStringLiteral("Warn about with statements as they can cause false "
+ "positives when checking for unqualified access"),
+ QtWarningMsg },
+ QQmlJS::LoggerCategory{ qmlInheritanceCycle.name().toString(),
+ QStringLiteral("InheritanceCycle"),
+ QStringLiteral("Warn about inheritance cycles"), QtWarningMsg },
+ QQmlJS::LoggerCategory{ qmlDeprecated.name().toString(), QStringLiteral("Deprecated"),
+ QStringLiteral("Warn about deprecated properties and types"),
+ QtWarningMsg },
+ QQmlJS::LoggerCategory{
qmlSignalParameters.name().toString(), QStringLiteral("BadSignalHandlerParameters"),
QStringLiteral("Warn about bad signal handler parameters"), QtWarningMsg },
- QQmlJSLogger::Category { qmlMissingType.name().toString(), QStringLiteral("MissingType"),
- QStringLiteral("Warn about missing types"), QtWarningMsg },
- QQmlJSLogger::Category { qmlUnresolvedType.name().toString(),
- QStringLiteral("UnresolvedType"),
- QStringLiteral("Warn about unresolved types"), QtWarningMsg },
- QQmlJSLogger::Category { qmlRestrictedType.name().toString(),
- QStringLiteral("RestrictedType"),
- QStringLiteral("Warn about restricted types"), QtWarningMsg },
- QQmlJSLogger::Category { qmlPrefixedImportType.name().toString(),
- QStringLiteral("PrefixedImportType"),
- QStringLiteral("Warn about prefixed import types"), QtWarningMsg },
- QQmlJSLogger::Category { qmlIncompatibleType.name().toString(),
- QStringLiteral("IncompatibleType"),
- QStringLiteral("Warn about missing types"), QtWarningMsg },
- QQmlJSLogger::Category { qmlMissingProperty.name().toString(),
- QStringLiteral("MissingProperty"),
- QStringLiteral("Warn about missing properties"), QtWarningMsg },
- QQmlJSLogger::Category { qmlNonListProperty.name().toString(),
- QStringLiteral("NonListProperty"),
- QStringLiteral("Warn about non-list properties"), QtWarningMsg },
- QQmlJSLogger::Category {
+ QQmlJS::LoggerCategory{ qmlMissingType.name().toString(), QStringLiteral("MissingType"),
+ QStringLiteral("Warn about missing types"), QtWarningMsg },
+ QQmlJS::LoggerCategory{ qmlUnresolvedType.name().toString(),
+ QStringLiteral("UnresolvedType"),
+ QStringLiteral("Warn about unresolved types"), QtWarningMsg },
+ QQmlJS::LoggerCategory{ qmlRestrictedType.name().toString(),
+ QStringLiteral("RestrictedType"),
+ QStringLiteral("Warn about restricted types"), QtWarningMsg },
+ QQmlJS::LoggerCategory{ qmlPrefixedImportType.name().toString(),
+ QStringLiteral("PrefixedImportType"),
+ QStringLiteral("Warn about prefixed import types"), QtWarningMsg },
+ QQmlJS::LoggerCategory{ qmlIncompatibleType.name().toString(),
+ QStringLiteral("IncompatibleType"),
+ QStringLiteral("Warn about missing types"), QtWarningMsg },
+ QQmlJS::LoggerCategory{ qmlMissingProperty.name().toString(),
+ QStringLiteral("MissingProperty"),
+ QStringLiteral("Warn about missing properties"), QtWarningMsg },
+ QQmlJS::LoggerCategory{ qmlNonListProperty.name().toString(),
+ QStringLiteral("NonListProperty"),
+ QStringLiteral("Warn about non-list properties"), QtWarningMsg },
+ QQmlJS::LoggerCategory{
qmlReadOnlyProperty.name().toString(), QStringLiteral("ReadOnlyProperty"),
QStringLiteral("Warn about writing to read-only properties"), QtWarningMsg },
- QQmlJSLogger::Category { qmlDuplicatePropertyBinding.name().toString(),
- QStringLiteral("DuplicatePropertyBinding"),
- QStringLiteral("Warn about duplicate property bindings"),
- QtWarningMsg },
- QQmlJSLogger::Category { qmlDuplicatedName.name().toString(),
- QStringLiteral("DuplicatedName"),
- QStringLiteral("Warn about duplicated property/signal names"),
- QtWarningMsg },
- QQmlJSLogger::Category {
+ QQmlJS::LoggerCategory{ qmlDuplicatePropertyBinding.name().toString(),
+ QStringLiteral("DuplicatePropertyBinding"),
+ QStringLiteral("Warn about duplicate property bindings"),
+ QtWarningMsg },
+ QQmlJS::LoggerCategory{
+ qmlDuplicatedName.name().toString(), QStringLiteral("DuplicatedName"),
+ QStringLiteral("Warn about duplicated property/signal names"), QtWarningMsg },
+ QQmlJS::LoggerCategory{
qmlDeferredPropertyId.name().toString(), QStringLiteral("DeferredPropertyId"),
QStringLiteral(
"Warn about making deferred properties immediate by giving them an id."),
QtInfoMsg, true, true },
- QQmlJSLogger::Category {
+ QQmlJS::LoggerCategory{
qmlUnqualified.name().toString(), QStringLiteral("UnqualifiedAccess"),
QStringLiteral("Warn about unqualified identifiers and how to fix them"),
QtWarningMsg },
- QQmlJSLogger::Category { qmlUnusedImports.name().toString(),
- QStringLiteral("UnusedImports"),
- QStringLiteral("Warn about unused imports"), QtInfoMsg },
- QQmlJSLogger::Category { qmlMultilineStrings.name().toString(),
- QStringLiteral("MultilineStrings"),
- QStringLiteral("Warn about multiline strings"), QtInfoMsg },
- QQmlJSLogger::Category { qmlSyntax.name().toString(), QString(),
- QStringLiteral("Syntax errors"), QtWarningMsg, false, true },
- QQmlJSLogger::Category { qmlSyntaxIdQuotation.name().toString(), QString(),
- QStringLiteral("ID quotation"), QtWarningMsg, false, true },
- QQmlJSLogger::Category { qmlSyntaxDuplicateIds.name().toString(), QString(),
- QStringLiteral("ID duplication"), QtCriticalMsg, false, true },
- QQmlJSLogger::Category { qmlCompiler.name().toString(), QStringLiteral("CompilerWarnings"),
- QStringLiteral("Warn about compiler issues"), QtWarningMsg, true },
- QQmlJSLogger::Category {
+ QQmlJS::LoggerCategory{ qmlUnusedImports.name().toString(), QStringLiteral("UnusedImports"),
+ QStringLiteral("Warn about unused imports"), QtInfoMsg },
+ QQmlJS::LoggerCategory{ qmlMultilineStrings.name().toString(),
+ QStringLiteral("MultilineStrings"),
+ QStringLiteral("Warn about multiline strings"), QtInfoMsg },
+ QQmlJS::LoggerCategory{ qmlSyntax.name().toString(), QString(),
+ QStringLiteral("Syntax errors"), QtWarningMsg, false, true },
+ QQmlJS::LoggerCategory{ qmlSyntaxIdQuotation.name().toString(), QString(),
+ QStringLiteral("ID quotation"), QtWarningMsg, false, true },
+ QQmlJS::LoggerCategory{ qmlSyntaxDuplicateIds.name().toString(), QString(),
+ QStringLiteral("ID duplication"), QtCriticalMsg, false, true },
+ QQmlJS::LoggerCategory{ qmlCompiler.name().toString(), QStringLiteral("CompilerWarnings"),
+ QStringLiteral("Warn about compiler issues"), QtWarningMsg, true },
+ QQmlJS::LoggerCategory{
qmlAttachedPropertyReuse.name().toString(), QStringLiteral("AttachedPropertyReuse"),
- QStringLiteral("Warn if attached types from parent components aren't reused"),
+ QStringLiteral("Warn if attached types from parent components "
+ "aren't reused. This is handled by the QtQuick "
+ "lint plugin. Use Quick.AttachedPropertyReuse instead."),
QtCriticalMsg, true },
- QQmlJSLogger::Category { qmlPlugin.name().toString(), QStringLiteral("LintPluginWarnings"),
- QStringLiteral("Warn if a qmllint plugin finds an issue"),
- QtWarningMsg, true },
- QQmlJSLogger::Category { qmlVarUsedBeforeDeclaration.name().toString(),
- QStringLiteral("VarUsedBeforeDeclaration"),
- QStringLiteral("Warn if a variable is used before declaration"),
- QtWarningMsg },
- QQmlJSLogger::Category {
+ QQmlJS::LoggerCategory{ qmlPlugin.name().toString(), QStringLiteral("LintPluginWarnings"),
+ QStringLiteral("Warn if a qmllint plugin finds an issue"),
+ QtWarningMsg, true },
+ QQmlJS::LoggerCategory{ qmlVarUsedBeforeDeclaration.name().toString(),
+ QStringLiteral("VarUsedBeforeDeclaration"),
+ QStringLiteral("Warn if a variable is used before declaration"),
+ QtWarningMsg },
+ QQmlJS::LoggerCategory{
qmlInvalidLintDirective.name().toString(), QStringLiteral("InvalidLintDirective"),
QStringLiteral("Warn if an invalid qmllint comment is found"), QtWarningMsg },
- QQmlJSLogger::Category {
+ QQmlJS::LoggerCategory{
qmlUseProperFunction.name().toString(), QStringLiteral("UseProperFunction"),
QStringLiteral("Warn if var is used for storing functions"), QtWarningMsg },
- QQmlJSLogger::Category {
+ QQmlJS::LoggerCategory{
qmlAccessSingleton.name().toString(), QStringLiteral("AccessSingletonViaObject"),
QStringLiteral("Warn if a singleton is accessed via an object"), QtWarningMsg },
- QQmlJSLogger::Category {
- qmlTopLevelComponent.name().toString(), QStringLiteral("TopLevelComponent"),
- QStringLiteral("Fail when a top level Component are encountered"), QtWarningMsg },
- QQmlJSLogger::Category {
- qmlUncreatableType.name().toString(), QStringLiteral("UncreatableType"),
- QStringLiteral("Warn if uncreatable types are created"), QtWarningMsg }
+ QQmlJS::LoggerCategory{
+ qmlTopLevelComponent.name().toString(), QStringLiteral("TopLevelComponent"),
+ QStringLiteral("Fail when a top level Component are encountered"), QtWarningMsg },
+ QQmlJS::LoggerCategory{
+ qmlUncreatableType.name().toString(), QStringLiteral("UncreatableType"),
+ QStringLiteral("Warn if uncreatable types are created"), QtWarningMsg }
};
return cats;
}
-const QList<QQmlJSLogger::Category> QQmlJSLogger::categories() const
+bool QQmlJSFixSuggestion::operator==(const QQmlJSFixSuggestion &other) const
{
- return m_categories.values();
+ return m_location == other.m_location && m_fixDescription == other.m_fixDescription
+ && m_replacement == other.m_replacement && m_filename == other.m_filename
+ && m_hint == other.m_hint && m_autoApplicable == other.m_autoApplicable;
}
-void QQmlJSLogger::registerCategory(const QQmlJSLogger::Category &category)
+bool QQmlJSFixSuggestion::operator!=(const QQmlJSFixSuggestion &other) const
{
- if (m_categories.contains(category.name)) {
- qWarning() << "Trying to re-register existing logger category" << category.name;
- return;
- }
-
- m_categoryLevels[category.name] = category.level;
- m_categoryIgnored[category.name] = category.ignored;
- m_categories.insert(category.name, category);
+ return !(*this == other);
}
-QQmlJSLogger::QQmlJSLogger()
+QList<QQmlJS::LoggerCategory> QQmlJSLogger::categories() const
{
- static const QList<QQmlJSLogger::Category> cats = defaultCategories();
+ return m_categories.values();
+}
- for (const QQmlJSLogger::Category &category : cats)
- registerCategory(category);
+void QQmlJSLogger::registerCategory(const QQmlJS::LoggerCategory &category)
+{
+ if (m_categories.contains(category.name())) {
+ qWarning() << "Trying to re-register existing logger category" << category.name();
+ return;
+ }
- // setup color output
- m_output.insertMapping(QtCriticalMsg, QColorOutput::RedForeground);
- m_output.insertMapping(QtWarningMsg, QColorOutput::PurpleForeground); // Yellow?
- m_output.insertMapping(QtInfoMsg, QColorOutput::BlueForeground);
- m_output.insertMapping(QtDebugMsg, QColorOutput::GreenForeground); // None?
+ m_categoryLevels[category.name()] = category.level();
+ m_categoryIgnored[category.name()] = category.isIgnored();
+ m_categories.insert(category.name(), category);
}
static bool isMsgTypeLess(QtMsgType a, QtMsgType b)
@@ -217,9 +232,9 @@ static bool isMsgTypeLess(QtMsgType a, QtMsgType b)
return level[a] < level[b];
}
-void QQmlJSLogger::log(const QString &message, LoggerWarningId id,
+void QQmlJSLogger::log(const QString &message, QQmlJS::LoggerWarningId id,
const QQmlJS::SourceLocation &srcLocation, QtMsgType type, bool showContext,
- bool showFileName, const std::optional<FixSuggestion> &suggestion,
+ bool showFileName, const std::optional<QQmlJSFixSuggestion> &suggestion,
const QString overrideFileName)
{
Q_ASSERT(m_categoryLevels.contains(id.name().toString()));
@@ -275,7 +290,7 @@ void QQmlJSLogger::log(const QString &message, LoggerWarningId id,
}
void QQmlJSLogger::processMessages(const QList<QQmlJS::DiagnosticMessage> &messages,
- LoggerWarningId id)
+ QQmlJS::LoggerWarningId id)
{
if (messages.isEmpty() || isCategoryIgnored(id))
return;
@@ -324,59 +339,65 @@ void QQmlJSLogger::printContext(const QString &overrideFileName,
+ QString::fromLatin1("^").repeated(locationLength) + QLatin1Char('\n'));
}
-void QQmlJSLogger::printFix(const FixSuggestion &fix)
+void QQmlJSLogger::printFix(const QQmlJSFixSuggestion &fixItem)
{
const QString currentFileAbsPath = QFileInfo(m_fileName).absolutePath();
QString code = m_code;
QString currentFile;
- for (const auto &fixItem : fix.fixes) {
- m_output.writePrefixedMessage(fixItem.message, QtInfoMsg);
-
- if (!fixItem.cutLocation.isValid())
- continue;
-
- if (fixItem.fileName == currentFile) {
- // Nothing to do in this case, we've already read the code
- } else if (fixItem.fileName.isEmpty() || fixItem.fileName == currentFileAbsPath) {
- code = m_code;
- } else {
- QFile file(fixItem.fileName);
- const bool success = file.open(QFile::ReadOnly);
- Q_ASSERT(success);
- code = QString::fromUtf8(file.readAll());
- currentFile = fixItem.fileName;
- }
-
- IssueLocationWithContext issueLocationWithContext { code, fixItem.cutLocation };
-
- if (const QStringView beforeText = issueLocationWithContext.beforeText();
- !beforeText.isEmpty()) {
- m_output.write(beforeText);
- }
-
- // The replacement string can be empty if we're only pointing something out to the user
- QStringView replacementString = fixItem.replacementString.isEmpty()
- ? issueLocationWithContext.issueText()
- : fixItem.replacementString;
-
- // But if there's nothing to change it has to be a hint
- if (fixItem.replacementString.isEmpty())
- Q_ASSERT(fixItem.isHint);
-
- m_output.write(replacementString, QtDebugMsg);
- m_output.write(issueLocationWithContext.afterText().toString() + u'\n');
-
- int tabCount = issueLocationWithContext.beforeText().count(u'\t');
-
- // Do not draw location indicator for multiline replacement strings
- if (replacementString.contains(u'\n'))
- continue;
-
- m_output.write(u" "_s.repeated(
- issueLocationWithContext.beforeText().size() - tabCount)
- + u"\t"_s.repeated(tabCount)
- + u"^"_s.repeated(fixItem.replacementString.size()) + u'\n');
+ m_output.writePrefixedMessage(fixItem.fixDescription(), QtInfoMsg);
+
+ if (!fixItem.location().isValid())
+ return;
+
+ const QString filename = fixItem.filename();
+ if (filename == currentFile) {
+ // Nothing to do in this case, we've already read the code
+ } else if (filename.isEmpty() || filename == currentFileAbsPath) {
+ code = m_code;
+ } else {
+ QFile file(filename);
+ const bool success = file.open(QFile::ReadOnly);
+ Q_ASSERT(success);
+ code = QString::fromUtf8(file.readAll());
+ currentFile = filename;
+ }
+
+ IssueLocationWithContext issueLocationWithContext { code, fixItem.location() };
+
+ if (const QStringView beforeText = issueLocationWithContext.beforeText();
+ !beforeText.isEmpty()) {
+ m_output.write(beforeText);
}
+
+ // The replacement string can be empty if we're only pointing something out to the user
+ const QString replacement = fixItem.replacement();
+ QStringView replacementString = replacement.isEmpty()
+ ? issueLocationWithContext.issueText()
+ : replacement;
+
+ // But if there's nothing to change it cannot be auto-applied
+ Q_ASSERT(!replacement.isEmpty() || !fixItem.isAutoApplicable());
+
+ m_output.write(replacementString, QtDebugMsg);
+ m_output.write(issueLocationWithContext.afterText().toString() + u'\n');
+
+ int tabCount = issueLocationWithContext.beforeText().count(u'\t');
+
+ // Do not draw location indicator for multiline replacement strings
+ if (replacementString.contains(u'\n'))
+ return;
+
+ m_output.write(u" "_s.repeated(
+ issueLocationWithContext.beforeText().size() - tabCount)
+ + u"\t"_s.repeated(tabCount)
+ + u"^"_s.repeated(replacement.size()) + u'\n');
+}
+
+QQmlJSFixSuggestion::QQmlJSFixSuggestion(const QString &fixDescription,
+ const QQmlJS::SourceLocation &location,
+ const QString &replacement)
+ : m_location{ location }, m_fixDescription{ fixDescription }, m_replacement{ replacement }
+{
}
QT_END_NAMESPACE
diff --git a/src/qmlcompiler/qqmljslogger_p.h b/src/qmlcompiler/qqmljslogger_p.h
index ca76bb5f37..d7c600d311 100644
--- a/src/qmlcompiler/qqmljslogger_p.h
+++ b/src/qmlcompiler/qqmljslogger_p.h
@@ -15,9 +15,10 @@
// We mean it.
//
-#include <private/qtqmlcompilerexports_p.h>
+#include <qtqmlcompilerexports.h>
#include "qcoloroutput_p.h"
+#include "qqmljsloggingutils_p.h"
#include <private/qqmljsdiagnosticmessage_p.h>
@@ -34,9 +35,9 @@ QT_BEGIN_NAMESPACE
/*!
\internal
- Used to print the the line containing the location of a certain error
+ Used to print the line containing the location of a certain error
*/
-class Q_QMLCOMPILER_PRIVATE_EXPORT IssueLocationWithContext
+class Q_QMLCOMPILER_EXPORT IssueLocationWithContext
{
public:
/*!
@@ -70,134 +71,54 @@ private:
QStringView m_afterText;
};
-struct Q_QMLCOMPILER_PRIVATE_EXPORT FixSuggestion
-{
- struct Fix
- {
- QString message;
- QQmlJS::SourceLocation cutLocation = QQmlJS::SourceLocation();
- QString replacementString = QString();
- QString fileName = QString();
- // A Fix is a hint if it can not be automatically applied to fix an issue or only points out
- // its origin
- bool isHint = true;
- };
- QList<Fix> fixes;
-};
-
-class Q_QMLCOMPILER_PRIVATE_EXPORT LoggerWarningId
+class Q_QMLCOMPILER_EXPORT QQmlJSFixSuggestion
{
public:
- constexpr LoggerWarningId(QAnyStringView name) : m_name(name) { }
+ QQmlJSFixSuggestion() = default;
+ QQmlJSFixSuggestion(const QString &fixDescription, const QQmlJS::SourceLocation &location,
+ const QString &replacement = QString());
+
+ QString fixDescription() const { return m_fixDescription; }
+ QQmlJS::SourceLocation location() const { return m_location; }
+ QString replacement() const { return m_replacement; }
+
+ void setFilename(const QString &filename) { m_filename = filename; }
+ QString filename() const { return m_filename; }
- const QAnyStringView name() const { return m_name; }
+ void setHint(const QString &hint) { m_hint = hint; }
+ QString hint() const { return m_hint; }
+
+ void setAutoApplicable(bool autoApply = true) { m_autoApplicable = autoApply; }
+ bool isAutoApplicable() const { return m_autoApplicable; }
+
+ bool operator==(const QQmlJSFixSuggestion &) const;
+ bool operator!=(const QQmlJSFixSuggestion &) const;
private:
- const QAnyStringView m_name;
+ QQmlJS::SourceLocation m_location;
+ QString m_fixDescription;
+ QString m_replacement;
+ QString m_filename;
+ QString m_hint;
+ bool m_autoApplicable = false;
};
-extern const Q_QMLCOMPILER_PRIVATE_EXPORT LoggerWarningId qmlRequired;
-extern const Q_QMLCOMPILER_PRIVATE_EXPORT LoggerWarningId qmlUnresolvedAlias;
-extern const Q_QMLCOMPILER_PRIVATE_EXPORT LoggerWarningId qmlAliasCycle;
-extern const Q_QMLCOMPILER_PRIVATE_EXPORT LoggerWarningId qmlImport;
-extern const Q_QMLCOMPILER_PRIVATE_EXPORT LoggerWarningId qmlRecursionDepthErrors;
-extern const Q_QMLCOMPILER_PRIVATE_EXPORT LoggerWarningId qmlWith;
-extern const Q_QMLCOMPILER_PRIVATE_EXPORT LoggerWarningId qmlInheritanceCycle;
-extern const Q_QMLCOMPILER_PRIVATE_EXPORT LoggerWarningId qmlDeprecated;
-extern const Q_QMLCOMPILER_PRIVATE_EXPORT LoggerWarningId qmlSignalParameters;
-extern const Q_QMLCOMPILER_PRIVATE_EXPORT LoggerWarningId qmlMissingType;
-extern const Q_QMLCOMPILER_PRIVATE_EXPORT LoggerWarningId qmlUnresolvedType;
-extern const Q_QMLCOMPILER_PRIVATE_EXPORT LoggerWarningId qmlIncompatibleType;
-extern const Q_QMLCOMPILER_PRIVATE_EXPORT LoggerWarningId qmlMissingProperty;
-extern const Q_QMLCOMPILER_PRIVATE_EXPORT LoggerWarningId qmlRestrictedType;
-extern const Q_QMLCOMPILER_PRIVATE_EXPORT LoggerWarningId qmlPrefixedImportType;
-extern const Q_QMLCOMPILER_PRIVATE_EXPORT LoggerWarningId qmlNonListProperty;
-extern const Q_QMLCOMPILER_PRIVATE_EXPORT LoggerWarningId qmlReadOnlyProperty;
-extern const Q_QMLCOMPILER_PRIVATE_EXPORT LoggerWarningId qmlDuplicatePropertyBinding;
-extern const Q_QMLCOMPILER_PRIVATE_EXPORT LoggerWarningId qmlDuplicatedName;
-extern const Q_QMLCOMPILER_PRIVATE_EXPORT LoggerWarningId qmlDeferredPropertyId;
-extern const Q_QMLCOMPILER_PRIVATE_EXPORT LoggerWarningId qmlUnqualified;
-extern const Q_QMLCOMPILER_PRIVATE_EXPORT LoggerWarningId qmlUnusedImports;
-extern const Q_QMLCOMPILER_PRIVATE_EXPORT LoggerWarningId qmlMultilineStrings;
-extern const Q_QMLCOMPILER_PRIVATE_EXPORT LoggerWarningId qmlSyntax;
-extern const Q_QMLCOMPILER_PRIVATE_EXPORT LoggerWarningId qmlSyntaxIdQuotation;
-extern const Q_QMLCOMPILER_PRIVATE_EXPORT LoggerWarningId qmlSyntaxDuplicateIds;
-extern const Q_QMLCOMPILER_PRIVATE_EXPORT LoggerWarningId qmlCompiler;
-extern const Q_QMLCOMPILER_PRIVATE_EXPORT LoggerWarningId qmlAttachedPropertyReuse;
-extern const Q_QMLCOMPILER_PRIVATE_EXPORT LoggerWarningId qmlPlugin;
-extern const Q_QMLCOMPILER_PRIVATE_EXPORT LoggerWarningId qmlVarUsedBeforeDeclaration;
-extern const Q_QMLCOMPILER_PRIVATE_EXPORT LoggerWarningId qmlInvalidLintDirective;
-extern const Q_QMLCOMPILER_PRIVATE_EXPORT LoggerWarningId qmlUseProperFunction;
-extern const Q_QMLCOMPILER_PRIVATE_EXPORT LoggerWarningId qmlAccessSingleton;
-extern const Q_QMLCOMPILER_PRIVATE_EXPORT LoggerWarningId qmlTopLevelComponent;
-extern const Q_QMLCOMPILER_PRIVATE_EXPORT LoggerWarningId qmlUncreatableType;
-
struct Message : public QQmlJS::DiagnosticMessage
{
// This doesn't need to be an owning-reference since the string is expected to outlive any
// Message object by virtue of coming from a LoggerWarningId.
QAnyStringView id;
- std::optional<FixSuggestion> fixSuggestion;
+ std::optional<QQmlJSFixSuggestion> fixSuggestion;
};
-class Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSLogger
+class Q_QMLCOMPILER_EXPORT QQmlJSLogger
{
Q_DISABLE_COPY_MOVE(QQmlJSLogger)
public:
- struct Category
- {
- QString name;
- QString settingsName;
- QString description;
- QtMsgType level;
- bool ignored = false;
- bool isDefault = false; // Whether or not the category can be disabled
- bool changed = false;
-
- bool operator==(const LoggerWarningId warningId) const { return warningId.name() == name; }
-
- LoggerWarningId id() const { return LoggerWarningId(name); }
-
- QString levelToString() const {
- // TODO: this only makes sense to qmllint
- Q_ASSERT(ignored || level != QtCriticalMsg);
- if (ignored)
- return QStringLiteral("disable");
-
- switch (level) {
- case QtInfoMsg:
- return QStringLiteral("info");
- case QtWarningMsg:
- return QStringLiteral("warning");
- default:
- Q_UNREACHABLE();
- break;
- }
- }
-
- bool setLevel(const QString &level) {
- if (level == QStringLiteral("disable")) {
- this->level = QtCriticalMsg; // TODO: only so for consistency with previous logic
- this->ignored = true;
- } else if (level == QStringLiteral("info")) {
- this->level = QtInfoMsg;
- this->ignored = false;
- } else if (level == QStringLiteral("warning")) {
- this->level = QtWarningMsg;
- this->ignored = false;
- } else {
- return false;
- }
-
- this->changed = true;
- return true;
- }
- };
-
- const QList<Category> categories() const;
- static const QList<Category> &defaultCategories();
-
- void registerCategory(const Category &category);
+ QList<QQmlJS::LoggerCategory> categories() const;
+ static const QList<QQmlJS::LoggerCategory> &defaultCategories();
+
+ void registerCategory(const QQmlJS::LoggerCategory &category);
QQmlJSLogger();
~QQmlJSLogger() = default;
@@ -209,37 +130,37 @@ public:
const QList<Message> &warnings() const { return m_warnings; }
const QList<Message> &errors() const { return m_errors; }
- QtMsgType categoryLevel(LoggerWarningId id) const
+ QtMsgType categoryLevel(QQmlJS::LoggerWarningId id) const
{
return m_categoryLevels[id.name().toString()];
}
- void setCategoryLevel(LoggerWarningId id, QtMsgType level)
+ void setCategoryLevel(QQmlJS::LoggerWarningId id, QtMsgType level)
{
m_categoryLevels[id.name().toString()] = level;
m_categoryChanged[id.name().toString()] = true;
}
- bool isCategoryIgnored(LoggerWarningId id) const
+ bool isCategoryIgnored(QQmlJS::LoggerWarningId id) const
{
return m_categoryIgnored[id.name().toString()];
}
- void setCategoryIgnored(LoggerWarningId id, bool error)
+ void setCategoryIgnored(QQmlJS::LoggerWarningId id, bool error)
{
m_categoryIgnored[id.name().toString()] = error;
m_categoryChanged[id.name().toString()] = true;
}
- bool isCategoryFatal(LoggerWarningId id) const
+ bool isCategoryFatal(QQmlJS::LoggerWarningId id) const
{
return m_categoryFatal[id.name().toString()];
}
- void setCategoryFatal(LoggerWarningId id, bool error)
+ void setCategoryFatal(QQmlJS::LoggerWarningId id, bool error)
{
m_categoryFatal[id.name().toString()] = error;
m_categoryChanged[id.name().toString()] = true;
}
- bool wasCategoryChanged(LoggerWarningId id) const
+ bool wasCategoryChanged(QQmlJS::LoggerWarningId id) const
{
return m_categoryChanged[id.name().toString()];
}
@@ -251,9 +172,9 @@ public:
\sa setCategoryLevel
*/
- void log(const QString &message, LoggerWarningId id, const QQmlJS::SourceLocation &srcLocation,
- bool showContext = true, bool showFileName = true,
- const std::optional<FixSuggestion> &suggestion = {},
+ void log(const QString &message, QQmlJS::LoggerWarningId id,
+ const QQmlJS::SourceLocation &srcLocation, bool showContext = true,
+ bool showFileName = true, const std::optional<QQmlJSFixSuggestion> &suggestion = {},
const QString overrideFileName = QString())
{
log(message, id, srcLocation, m_categoryLevels[id.name().toString()], showContext,
@@ -261,7 +182,7 @@ public:
}
void processMessages(const QList<QQmlJS::DiagnosticMessage> &messages,
- const LoggerWarningId id);
+ const QQmlJS::LoggerWarningId id);
void ignoreWarnings(uint32_t line, const QSet<QString> &categories)
{
@@ -278,14 +199,15 @@ public:
QString fileName() const { return m_fileName; }
private:
- QMap<QString, Category> m_categories;
+ QMap<QString, QQmlJS::LoggerCategory> m_categories;
void printContext(const QString &overrideFileName, const QQmlJS::SourceLocation &location);
- void printFix(const FixSuggestion &fix);
+ void printFix(const QQmlJSFixSuggestion &fix);
- void log(const QString &message, LoggerWarningId id, const QQmlJS::SourceLocation &srcLocation,
- QtMsgType type, bool showContext, bool showFileName,
- const std::optional<FixSuggestion> &suggestion, const QString overrideFileName);
+ void log(const QString &message, QQmlJS::LoggerWarningId id,
+ const QQmlJS::SourceLocation &srcLocation, QtMsgType type, bool showContext,
+ bool showFileName, const std::optional<QQmlJSFixSuggestion> &suggestion,
+ const QString overrideFileName);
QString m_fileName;
QString m_code;
diff --git a/src/qmlcompiler/qqmljsloggingutils.cpp b/src/qmlcompiler/qqmljsloggingutils.cpp
new file mode 100644
index 0000000000..caa2438ae5
--- /dev/null
+++ b/src/qmlcompiler/qqmljsloggingutils.cpp
@@ -0,0 +1,136 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "qqmljsloggingutils.h"
+#include "qqmljsloggingutils_p.h"
+
+QT_BEGIN_NAMESPACE
+
+namespace QQmlJS {
+
+LoggerCategory::LoggerCategory() : d_ptr{ new LoggerCategoryPrivate } { }
+
+LoggerCategory::LoggerCategory(QString name, QString settingsName, QString description,
+ QtMsgType level, bool ignored, bool isDefault)
+ : d_ptr{ new LoggerCategoryPrivate }
+{
+ Q_D(LoggerCategory);
+ d->m_name = name;
+ d->m_settingsName = settingsName;
+ d->m_description = description;
+ d->m_level = level;
+ d->m_ignored = ignored;
+ d->m_isDefault = isDefault;
+}
+
+LoggerCategory::LoggerCategory(const LoggerCategory &other)
+ : d_ptr{ new LoggerCategoryPrivate{ *other.d_func() } }
+{
+}
+
+LoggerCategory::LoggerCategory(LoggerCategory &&) noexcept = default;
+
+LoggerCategory &LoggerCategory::operator=(const LoggerCategory &other)
+{
+ *d_func() = *other.d_func();
+ return *this;
+}
+
+LoggerCategory &LoggerCategory::operator=(LoggerCategory &&) noexcept = default;
+
+LoggerCategory::~LoggerCategory() = default;
+
+QString LoggerCategory::name() const
+{
+ Q_D(const LoggerCategory);
+ return d->m_name;
+}
+
+QString LoggerCategory::settingsName() const
+{
+ Q_D(const LoggerCategory);
+ return d->m_settingsName;
+}
+
+QString LoggerCategory::description() const
+{
+ Q_D(const LoggerCategory);
+ return d->m_description;
+}
+
+QtMsgType LoggerCategory::level() const
+{
+ Q_D(const LoggerCategory);
+ return d->m_level;
+}
+
+bool LoggerCategory::isIgnored() const
+{
+ Q_D(const LoggerCategory);
+ return d->m_ignored;
+}
+
+bool LoggerCategory::isDefault() const
+{
+ Q_D(const LoggerCategory);
+ return d->m_isDefault;
+}
+
+LoggerWarningId LoggerCategory::id() const
+{
+ Q_D(const LoggerCategory);
+ return d->id();
+}
+
+void LoggerCategory::setLevel(QtMsgType type)
+{
+ Q_D(LoggerCategory);
+ d->setLevel(type);
+}
+
+void LoggerCategoryPrivate::setLevel(QtMsgType type)
+{
+ if (m_level == type)
+ return;
+
+ m_level = type;
+ m_changed = true;
+}
+
+void LoggerCategory::setIgnored(bool isIgnored)
+{
+ Q_D(LoggerCategory);
+ d->setIgnored(isIgnored);
+}
+
+void LoggerCategoryPrivate::setIgnored(bool isIgnored)
+{
+ if (m_ignored == isIgnored)
+ return;
+
+ m_ignored = isIgnored;
+ m_changed = true;
+}
+
+bool LoggerCategoryPrivate::hasChanged() const
+{
+ return m_changed;
+}
+
+LoggerCategoryPrivate *LoggerCategoryPrivate::get(LoggerCategory *loggerCategory)
+{
+ Q_ASSERT(loggerCategory);
+ return loggerCategory->d_func();
+}
+
+/*!
+ \class QQmlSA::LoggerWarningId
+ \inmodule QtQmlCompiler
+
+ \brief A wrapper around a string literal to uniquely identify
+ warning categories in the \c{QQmlSA} framework.
+*/
+
+} // namespace QQmlJS
+
+QT_END_NAMESPACE
diff --git a/src/qmlcompiler/qqmljsloggingutils.h b/src/qmlcompiler/qqmljsloggingutils.h
new file mode 100644
index 0000000000..5d48cd379b
--- /dev/null
+++ b/src/qmlcompiler/qqmljsloggingutils.h
@@ -0,0 +1,82 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef QQMLJSLOGGER_H
+#define QQMLJSLOGGER_H
+
+#include <QtCore/qanystringview.h>
+#include <QtQmlCompiler/qtqmlcompilerexports.h>
+
+QT_BEGIN_NAMESPACE
+
+class QQmlJSLoggerPrivate;
+class QQmlJSScope;
+
+namespace QQmlSA {
+class SourceLocation;
+}
+
+namespace QQmlSA {
+
+class Q_QMLCOMPILER_EXPORT LoggerWarningId
+{
+public:
+ constexpr LoggerWarningId(QAnyStringView name) : m_name(name) { }
+
+ QAnyStringView name() const { return m_name; }
+
+private:
+ friend bool operator==(const LoggerWarningId &a, const LoggerWarningId &b)
+ {
+ return a.m_name == b.m_name;
+ }
+
+ friend bool operator!=(const LoggerWarningId &a, const LoggerWarningId &b)
+ {
+ return a.m_name != b.m_name;
+ }
+ const QAnyStringView m_name;
+};
+
+} // namespace QQmlSA
+
+extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlRequired;
+extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlUnresolvedAlias;
+extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlAliasCycle;
+extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlImport;
+extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlRecursionDepthErrors;
+extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlWith;
+extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlInheritanceCycle;
+extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlDeprecated;
+extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlSignalParameters;
+extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlMissingType;
+extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlUnresolvedType;
+extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlIncompatibleType;
+extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlMissingProperty;
+extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlRestrictedType;
+extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlPrefixedImportType;
+extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlNonListProperty;
+extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlReadOnlyProperty;
+extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlDuplicatePropertyBinding;
+extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlDuplicatedName;
+extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlDeferredPropertyId;
+extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlUnqualified;
+extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlUnusedImports;
+extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlMultilineStrings;
+extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlSyntax;
+extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlSyntaxIdQuotation;
+extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlSyntaxDuplicateIds;
+extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlCompiler;
+extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlAttachedPropertyReuse;
+extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlPlugin;
+extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlVarUsedBeforeDeclaration;
+extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlInvalidLintDirective;
+extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlUseProperFunction;
+extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlAccessSingleton;
+extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlTopLevelComponent;
+extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlUncreatableType;
+extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlMissingEnumEntry;
+
+QT_END_NAMESPACE
+
+#endif // QQMLJSLOGGER_H
diff --git a/src/qmlcompiler/qqmljsloggingutils_p.h b/src/qmlcompiler/qqmljsloggingutils_p.h
new file mode 100644
index 0000000000..9099a2c73c
--- /dev/null
+++ b/src/qmlcompiler/qqmljsloggingutils_p.h
@@ -0,0 +1,108 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef QQMLJSLOGGINGUTILS_P_H
+#define QQMLJSLOGGINGUTILS_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qstring.h>
+#include <qtqmlcompilerexports.h>
+
+#include "qqmljsloggingutils.h"
+
+QT_BEGIN_NAMESPACE
+
+namespace QQmlJS {
+
+using LoggerWarningId = QQmlSA::LoggerWarningId;
+
+class LoggerCategoryPrivate;
+
+class Q_QMLCOMPILER_EXPORT LoggerCategory
+{
+ Q_DECLARE_PRIVATE(LoggerCategory)
+
+public:
+ LoggerCategory();
+ LoggerCategory(QString name, QString settingsName, QString description, QtMsgType level,
+ bool ignored = false, bool isDefault = false);
+ LoggerCategory(const LoggerCategory &);
+ LoggerCategory(LoggerCategory &&) noexcept;
+ LoggerCategory &operator=(const LoggerCategory &);
+ LoggerCategory &operator=(LoggerCategory &&) noexcept;
+ ~LoggerCategory();
+
+ QString name() const;
+ QString settingsName() const;
+ QString description() const;
+ QtMsgType level() const;
+ bool isIgnored() const;
+ bool isDefault() const;
+
+ LoggerWarningId id() const;
+
+ void setLevel(QtMsgType);
+ void setIgnored(bool);
+
+private:
+ std::unique_ptr<QQmlJS::LoggerCategoryPrivate> d_ptr;
+};
+
+class LoggerCategoryPrivate
+{
+ friend class QT_PREPEND_NAMESPACE(QQmlJS::LoggerCategory);
+
+public:
+ LoggerWarningId id() const { return LoggerWarningId(m_name); }
+
+ void setLevel(QtMsgType);
+ void setIgnored(bool);
+
+ QString name() const;
+ QString settingsName() const;
+ QString description() const;
+ QtMsgType level() const;
+ bool isIgnored() const;
+ bool isDefault() const;
+ bool hasChanged() const;
+
+ static LoggerCategoryPrivate *get(LoggerCategory *);
+
+ friend bool operator==(const LoggerCategoryPrivate &lhs, const LoggerCategoryPrivate &rhs)
+ {
+ return operatorEqualsImpl(lhs, rhs);
+ }
+ friend bool operator!=(const LoggerCategoryPrivate &lhs, const LoggerCategoryPrivate &rhs)
+ {
+ return !operatorEqualsImpl(lhs, rhs);
+ }
+
+ bool operator==(const LoggerWarningId warningId) const { return warningId.name() == m_name; }
+
+private:
+ static bool operatorEqualsImpl(const LoggerCategoryPrivate &, const LoggerCategoryPrivate &);
+
+ QString m_name;
+ QString m_settingsName;
+ QString m_description;
+ QtMsgType m_level = QtDebugMsg;
+ bool m_ignored = false;
+ bool m_isDefault = false; // Whether or not the category can be disabled
+ bool m_changed = false;
+};
+
+} // namespace QQmlJS
+
+QT_END_NAMESPACE
+
+#endif // QQMLJSLOGGINGUTILS_P_H
diff --git a/src/qmlcompiler/qqmljsmetatypes.cpp b/src/qmlcompiler/qqmljsmetatypes.cpp
index 9ba7d2ca54..4155e58728 100644
--- a/src/qmlcompiler/qqmljsmetatypes.cpp
+++ b/src/qmlcompiler/qqmljsmetatypes.cpp
@@ -13,21 +13,9 @@ QT_BEGIN_NAMESPACE
A binding is valid when it has both a target (m_propertyName is set)
and some content set (m_bindingType != Invalid).
*/
-bool QQmlJSMetaPropertyBinding::isValid() const { return !m_propertyName.isEmpty() && bindingType() != Invalid; }
-
-QString QQmlJSMetaPropertyBinding::literalTypeName() const
+bool QQmlJSMetaPropertyBinding::isValid() const
{
- if (std::holds_alternative<Content::BoolLiteral>(m_bindingContent))
- return QLatin1String("bool");
- else if (std::holds_alternative<Content::NumberLiteral>(m_bindingContent))
- return QLatin1String("double");
- else if (std::holds_alternative<Content::StringLiteral>(m_bindingContent))
- return QLatin1String("string");
- else if (std::holds_alternative<Content::RegexpLiteral>(m_bindingContent))
- return QLatin1String("regexp");
- else if (std::holds_alternative<Content::Null>(m_bindingContent))
- return QLatin1String("$internal$.std::nullptr_t");
- return {};
+ return !m_propertyName.isEmpty() && bindingType() != QQmlSA::BindingType::Invalid;
}
bool QQmlJSMetaPropertyBinding::boolValue() const
@@ -93,28 +81,30 @@ QSharedPointer<const QQmlJSScope> QQmlJSMetaPropertyBinding::literalType(const Q
{
Q_ASSERT(resolver);
switch (bindingType()) {
- case QQmlJSMetaPropertyBinding::BoolLiteral:
+ case BindingType::BoolLiteral:
return resolver->boolType();
- case QQmlJSMetaPropertyBinding::NumberLiteral:
+ case BindingType::NumberLiteral:
return resolver->typeForName(QLatin1String("double"));
- case QQmlJSMetaPropertyBinding::Translation: // translations are strings
- case QQmlJSMetaPropertyBinding::TranslationById:
- case QQmlJSMetaPropertyBinding::StringLiteral:
+ case BindingType::Translation: // translations are strings
+ case BindingType::TranslationById:
+ case BindingType::StringLiteral:
return resolver->stringType();
- case QQmlJSMetaPropertyBinding::RegExpLiteral:
+ case BindingType::RegExpLiteral:
return resolver->typeForName(QLatin1String("regexp"));
- case QQmlJSMetaPropertyBinding::Null:
+ case BindingType::Null:
return resolver->nullType();
- case QQmlJSMetaPropertyBinding::Invalid:
- case QQmlJSMetaPropertyBinding::Script:
- case QQmlJSMetaPropertyBinding::Object:
- case QQmlJSMetaPropertyBinding::Interceptor:
- case QQmlJSMetaPropertyBinding::ValueSource:
- case QQmlJSMetaPropertyBinding::AttachedProperty:
- case QQmlJSMetaPropertyBinding::GroupProperty:
+ case BindingType::Invalid:
+ case BindingType::Script:
+ case BindingType::Object:
+ case BindingType::Interceptor:
+ case BindingType::ValueSource:
+ case BindingType::AttachedProperty:
+ case BindingType::GroupProperty:
return {};
}
Q_UNREACHABLE_RETURN({});
}
+QQmlJSMetaPropertyBinding::QQmlJSMetaPropertyBinding() = default;
+
QT_END_NAMESPACE
diff --git a/src/qmlcompiler/qqmljsmetatypes_p.h b/src/qmlcompiler/qqmljsmetatypes_p.h
index 47945558b7..f715f22ea7 100644
--- a/src/qmlcompiler/qqmljsmetatypes_p.h
+++ b/src/qmlcompiler/qqmljsmetatypes_p.h
@@ -14,7 +14,7 @@
//
// We mean it.
-#include <private/qtqmlcompilerexports_p.h>
+#include <qtqmlcompilerexports.h>
#include <QtCore/qstring.h>
#include <QtCore/qstringlist.h>
@@ -23,9 +23,10 @@
#include <QtCore/qhash.h>
#include <QtQml/private/qqmljssourcelocation_p.h>
-
#include <QtQml/private/qqmltranslation_p.h>
+#include "qqmlsaconstants.h"
+#include "qqmlsa.h"
#include "qqmljsannotation_p.h"
// MetaMethod and MetaProperty have both type names and actual QQmlJSScope types.
@@ -40,6 +41,13 @@
QT_BEGIN_NAMESPACE
+enum ScriptBindingValueType : unsigned int {
+ ScriptValue_Unknown,
+ ScriptValue_Undefined // property int p: undefined
+};
+
+using QQmlJSMetaMethodType = QQmlSA::MethodType;
+
class QQmlJSTypeResolver;
class QQmlJSScope;
class QQmlJSMetaEnum
@@ -48,9 +56,11 @@ class QQmlJSMetaEnum
QList<int> m_values; // empty if values unknown.
QString m_name;
QString m_alias;
+ QString m_typeName;
QSharedPointer<const QQmlJSScope> m_type;
bool m_isFlag = false;
- bool m_scoped = true;
+ bool m_isScoped = false;
+ bool m_isQml = false;
public:
QQmlJSMetaEnum() = default;
@@ -67,8 +77,11 @@ public:
bool isFlag() const { return m_isFlag; }
void setIsFlag(bool isFlag) { m_isFlag = isFlag; }
- bool isScoped() const { return m_scoped; }
- void setScoped(bool v) { m_scoped = v; }
+ bool isScoped() const { return m_isScoped; }
+ void setIsScoped(bool v) { m_isScoped = v; }
+
+ bool isQml() const { return m_isQml; }
+ void setIsQml(bool v) { m_isQml = v; }
void addKey(const QString &key) { m_keys.append(key); }
QStringList keys() const { return m_keys; }
@@ -80,6 +93,9 @@ public:
int value(const QString &key) const { return m_values.value(m_keys.indexOf(key)); }
bool hasKey(const QString &key) const { return m_keys.indexOf(key) != -1; }
+ QString typeName() const { return m_typeName; }
+ void setTypeName(const QString &typeName) { m_typeName = typeName; }
+
QSharedPointer<const QQmlJSScope> type() const { return m_type; }
void setType(const QSharedPointer<const QQmlJSScope> &type) { m_type = type; }
@@ -90,7 +106,8 @@ public:
&& a.m_name == b.m_name
&& a.m_alias == b.m_alias
&& a.m_isFlag == b.m_isFlag
- && a.m_type == b.m_type;
+ && a.m_type == b.m_type
+ && a.m_isScoped == b.m_isScoped;
}
friend bool operator!=(const QQmlJSMetaEnum &a, const QQmlJSMetaEnum &b)
@@ -100,7 +117,8 @@ public:
friend size_t qHash(const QQmlJSMetaEnum &e, size_t seed = 0)
{
- return qHashMulti(seed, e.m_keys, e.m_values, e.m_name, e.m_alias, e.m_isFlag, e.m_type);
+ return qHashMulti(
+ seed, e.m_keys, e.m_values, e.m_name, e.m_alias, e.m_isFlag, e.m_type, e.m_isScoped);
}
};
@@ -118,10 +136,13 @@ public:
Const,
};
- QQmlJSMetaParameter(const QString &name, const QString &typeName,
+ QQmlJSMetaParameter(QString name = QString(), QString typeName = QString(),
Constness typeQualifier = NonConst,
QWeakPointer<const QQmlJSScope> type = {})
- : m_name(name), m_typeName(typeName), m_type(type), m_typeQualifier(typeQualifier)
+ : m_name(std::move(name)),
+ m_typeName(std::move(typeName)),
+ m_type(type),
+ m_typeQualifier(typeQualifier)
{
}
@@ -135,11 +156,13 @@ public:
void setTypeQualifier(Constness typeQualifier) { m_typeQualifier = typeQualifier; }
bool isPointer() const { return m_isPointer; }
void setIsPointer(bool isPointer) { m_isPointer = isPointer; }
+ bool isList() const { return m_isList; }
+ void setIsList(bool isList) { m_isList = isList; }
friend bool operator==(const QQmlJSMetaParameter &a, const QQmlJSMetaParameter &b)
{
return a.m_name == b.m_name && a.m_typeName == b.m_typeName
- && a.m_type.toStrongRef().data() == b.m_type.toStrongRef().data()
+ && a.m_type.owner_equal(b.m_type)
&& a.m_typeQualifier == b.m_typeQualifier;
}
@@ -150,7 +173,7 @@ public:
friend size_t qHash(const QQmlJSMetaParameter &e, size_t seed = 0)
{
- return qHashMulti(seed, e.m_name, e.m_typeName, e.m_type.toStrongRef().data(),
+ return qHashMulti(seed, e.m_name, e.m_typeName, e.m_type.owner_hash(),
e.m_typeQualifier);
}
@@ -160,14 +183,16 @@ private:
QWeakPointer<const QQmlJSScope> m_type;
Constness m_typeQualifier = NonConst;
bool m_isPointer = false;
+ bool m_isList = false;
};
+using QQmlJSMetaReturnType = QQmlJSMetaParameter;
+
class QQmlJSMetaMethod
{
public:
- enum Type { Signal, Slot, Method, StaticMethod };
-
enum Access { Private, Protected, Public };
+ using MethodType = QQmlJSMetaMethodType;
public:
/*! \internal
@@ -188,23 +213,30 @@ public:
QQmlJSMetaMethod() = default;
explicit QQmlJSMetaMethod(QString name, QString returnType = QString())
- : m_name(std::move(name))
- , m_returnTypeName(std::move(returnType))
- , m_methodType(Method)
+ : m_name(std::move(name)),
+ m_returnType(QString(), std::move(returnType)),
+ m_methodType(MethodType::Method)
{}
QString methodName() const { return m_name; }
void setMethodName(const QString &name) { m_name = name; }
- QString returnTypeName() const { return m_returnTypeName; }
- QSharedPointer<const QQmlJSScope> returnType() const { return m_returnType.toStrongRef(); }
- void setReturnTypeName(const QString &type) { m_returnTypeName = type; }
- void setReturnType(const QSharedPointer<const QQmlJSScope> &type)
- {
- m_returnType = type;
- }
+ QQmlJS::SourceLocation sourceLocation() const { return m_sourceLocation; }
+ void setSourceLocation(QQmlJS::SourceLocation location) { m_sourceLocation = location; }
+
+ QQmlJSMetaReturnType returnValue() const { return m_returnType; }
+ void setReturnValue(const QQmlJSMetaReturnType returnValue) { m_returnType = returnValue; }
+ QString returnTypeName() const { return m_returnType.typeName(); }
+ void setReturnTypeName(const QString &typeName) { m_returnType.setTypeName(typeName); }
+ QSharedPointer<const QQmlJSScope> returnType() const { return m_returnType.type(); }
+ void setReturnType(QWeakPointer<const QQmlJSScope> type) { m_returnType.setType(type); }
QList<QQmlJSMetaParameter> parameters() const { return m_parameters; }
+ QPair<QList<QQmlJSMetaParameter>::iterator, QList<QQmlJSMetaParameter>::iterator>
+ mutableParametersRange()
+ {
+ return { m_parameters.begin(), m_parameters.end() };
+ }
QStringList parameterNames() const
{
@@ -219,8 +251,8 @@ public:
void addParameter(const QQmlJSMetaParameter &p) { m_parameters.append(p); }
- int methodType() const { return m_methodType; }
- void setMethodType(Type methodType) { m_methodType = methodType; }
+ QQmlJSMetaMethodType methodType() const { return m_methodType; }
+ void setMethodType(MethodType methodType) { m_methodType = methodType; }
Access access() const { return m_methodAccess; }
@@ -250,16 +282,36 @@ public:
const QVector<QQmlJSAnnotation>& annotations() const { return m_annotations; }
void setAnnotations(QVector<QQmlJSAnnotation> annotations) { m_annotations = annotations; }
- void setJsFunctionIndex(RelativeFunctionIndex index) { m_jsFunctionIndex = index; }
- RelativeFunctionIndex jsFunctionIndex() const { return m_jsFunctionIndex; }
+ void setJsFunctionIndex(RelativeFunctionIndex index)
+ {
+ Q_ASSERT(!m_isConstructor);
+ m_relativeFunctionIndex = index;
+ }
+
+ RelativeFunctionIndex jsFunctionIndex() const
+ {
+ Q_ASSERT(!m_isConstructor);
+ return m_relativeFunctionIndex;
+ }
+
+ void setConstructorIndex(RelativeFunctionIndex index)
+ {
+ Q_ASSERT(m_isConstructor);
+ m_relativeFunctionIndex = index;
+ }
+
+ RelativeFunctionIndex constructorIndex() const
+ {
+ Q_ASSERT(m_isConstructor);
+ return m_relativeFunctionIndex;
+ }
friend bool operator==(const QQmlJSMetaMethod &a, const QQmlJSMetaMethod &b)
{
- return a.m_name == b.m_name && a.m_returnTypeName == b.m_returnTypeName
- && a.m_returnType == b.m_returnType && a.m_parameters == b.m_parameters
- && a.m_annotations == b.m_annotations && a.m_methodType == b.m_methodType
- && a.m_methodAccess == b.m_methodAccess && a.m_revision == b.m_revision
- && a.m_isConstructor == b.m_isConstructor;
+ return a.m_name == b.m_name && a.m_returnType == b.m_returnType
+ && a.m_parameters == b.m_parameters && a.m_annotations == b.m_annotations
+ && a.m_methodType == b.m_methodType && a.m_methodAccess == b.m_methodAccess
+ && a.m_revision == b.m_revision && a.m_isConstructor == b.m_isConstructor;
}
friend bool operator!=(const QQmlJSMetaMethod &a, const QQmlJSMetaMethod &b)
@@ -272,8 +324,7 @@ public:
QtPrivate::QHashCombine combine;
seed = combine(seed, method.m_name);
- seed = combine(seed, method.m_returnTypeName);
- seed = combine(seed, method.m_returnType.toStrongRef().data());
+ seed = combine(seed, method.m_returnType);
seed = combine(seed, method.m_annotations);
seed = combine(seed, method.m_methodType);
seed = combine(seed, method.m_methodAccess);
@@ -289,16 +340,17 @@ public:
private:
QString m_name;
- QString m_returnTypeName;
- QWeakPointer<const QQmlJSScope> m_returnType;
+ QQmlJS::SourceLocation m_sourceLocation;
+
+ QQmlJSMetaReturnType m_returnType;
QList<QQmlJSMetaParameter> m_parameters;
QList<QQmlJSAnnotation> m_annotations;
- Type m_methodType = Signal;
+ MethodType m_methodType = MethodType::Signal;
Access m_methodAccess = Public;
int m_revision = 0;
- RelativeFunctionIndex m_jsFunctionIndex = RelativeFunctionIndex::Invalid;
+ RelativeFunctionIndex m_relativeFunctionIndex = RelativeFunctionIndex::Invalid;
bool m_isCloned = false;
bool m_isConstructor = false;
bool m_isJavaScriptFunction = false;
@@ -321,8 +373,9 @@ class QQmlJSMetaProperty
bool m_isList = false;
bool m_isWritable = false;
bool m_isPointer = false;
+ bool m_isTypeConstant = false;
bool m_isFinal = false;
- bool m_isConstant = false;
+ bool m_isPropertyConstant = false;
int m_revision = 0;
int m_index = -1; // relative property index within owning QQmlJSScope
@@ -369,6 +422,9 @@ public:
void setIsPointer(bool isPointer) { m_isPointer = isPointer; }
bool isPointer() const { return m_isPointer; }
+ void setIsTypeConstant(bool isTypeConstant) { m_isTypeConstant = isTypeConstant; }
+ bool isTypeConstant() const { return m_isTypeConstant; }
+
void setAliasExpression(const QString &aliasString) { m_aliasExpr = aliasString; }
QString aliasExpression() const { return m_aliasExpr; }
bool isAlias() const { return !m_aliasExpr.isEmpty(); } // exists for convenience
@@ -376,8 +432,8 @@ public:
void setIsFinal(bool isFinal) { m_isFinal = isFinal; }
bool isFinal() const { return m_isFinal; }
- void setIsConstant(bool isConstant) { m_isConstant = isConstant; }
- bool isConstant() const { return m_isConstant; }
+ void setIsPropertyConstant(bool isPropertyConstant) { m_isPropertyConstant = isPropertyConstant; }
+ bool isPropertyConstant() const { return m_isPropertyConstant; }
void setRevision(int revision) { m_revision = revision; }
int revision() const { return m_revision; }
@@ -391,7 +447,7 @@ public:
{
return a.m_index == b.m_index && a.m_propertyName == b.m_propertyName
&& a.m_typeName == b.m_typeName && a.m_bindable == b.m_bindable
- && a.m_type == b.m_type && a.m_isList == b.m_isList
+ && a.m_type.owner_equal(b.m_type) && a.m_isList == b.m_isList
&& a.m_isWritable == b.m_isWritable && a.m_isPointer == b.m_isPointer
&& a.m_aliasExpr == b.m_aliasExpr && a.m_revision == b.m_revision
&& a.m_isFinal == b.m_isFinal;
@@ -420,39 +476,10 @@ public:
create a new binding, you know all the details of it already, so you should
just set all the data at once.
*/
-class Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSMetaPropertyBinding
+class Q_QMLCOMPILER_EXPORT QQmlJSMetaPropertyBinding
{
-public:
- enum BindingType : unsigned int {
- Invalid,
- BoolLiteral,
- NumberLiteral,
- StringLiteral,
- RegExpLiteral,
- Null,
- Translation,
- TranslationById,
- Script,
- Object,
- Interceptor,
- ValueSource,
- AttachedProperty,
- GroupProperty,
- };
-
- enum ScriptBindingKind : unsigned int {
- Script_Invalid,
- Script_PropertyBinding, // property int p: 1 + 1
- Script_SignalHandler, // onSignal: { ... }
- Script_ChangeHandler, // onXChanged: { ... }
- };
-
- enum ScriptBindingValueType : unsigned int {
- ScriptValue_Unknown,
- ScriptValue_Undefined // property int p: undefined
- };
-
-private:
+ using BindingType = QQmlSA::BindingType;
+ using ScriptBindingKind = QQmlSA::ScriptBindingKind;
// needs to be kept in sync with the BindingType enum
struct Content {
@@ -514,11 +541,11 @@ private:
friend bool operator!=(Script a, Script b) { return !(a == b); }
QQmlJSMetaMethod::RelativeFunctionIndex index =
QQmlJSMetaMethod::RelativeFunctionIndex::Invalid;
- ScriptBindingKind kind = Script_Invalid;
- ScriptBindingValueType valueType = ScriptValue_Unknown;
+ ScriptBindingKind kind = ScriptBindingKind::Invalid;
+ ScriptBindingValueType valueType = ScriptBindingValueType::ScriptValue_Unknown;
};
struct Object {
- friend bool operator==(Object a, Object b) { return a.value == b.value && a.typeName == b.typeName; }
+ friend bool operator==(Object a, Object b) { return a.value.owner_equal(b.value) && a.typeName == b.typeName; }
friend bool operator!=(Object a, Object b) { return !(a == b); }
QString typeName;
QWeakPointer<const QQmlJSScope> value;
@@ -526,7 +553,7 @@ private:
struct Interceptor {
friend bool operator==(Interceptor a, Interceptor b)
{
- return a.value == b.value && a.typeName == b.typeName;
+ return a.value.owner_equal(b.value) && a.typeName == b.typeName;
}
friend bool operator!=(Interceptor a, Interceptor b) { return !(a == b); }
QString typeName;
@@ -535,7 +562,7 @@ private:
struct ValueSource {
friend bool operator==(ValueSource a, ValueSource b)
{
- return a.value == b.value && a.typeName == b.typeName;
+ return a.value.owner_equal(b.value) && a.typeName == b.typeName;
}
friend bool operator!=(ValueSource a, ValueSource b) { return !(a == b); }
QString typeName;
@@ -564,7 +591,7 @@ private:
*/
friend bool operator==(AttachedProperty a, AttachedProperty b)
{
- return a.value == b.value;
+ return a.value.owner_equal(b.value);
}
friend bool operator!=(AttachedProperty a, AttachedProperty b) { return !(a == b); }
QWeakPointer<const QQmlJSScope> value;
@@ -586,7 +613,7 @@ private:
### TODO: Obtaining the effective binding result requires some resolving function
*/
QWeakPointer<const QQmlJSScope> groupScope;
- friend bool operator==(GroupProperty a, GroupProperty b) { return a.groupScope == b.groupScope; }
+ friend bool operator==(GroupProperty a, GroupProperty b) { return a.groupScope.owner_equal(b.groupScope); }
friend bool operator!=(GroupProperty a, GroupProperty b) { return !(a == b); }
};
using type = std::variant<Invalid, BoolLiteral, NumberLiteral, StringLiteral,
@@ -617,6 +644,7 @@ public:
|| type == BindingType::Null; // special. we record it as literal
}
+ QQmlJSMetaPropertyBinding();
QQmlJSMetaPropertyBinding(QQmlJS::SourceLocation location) : m_sourceLocation(location) { }
explicit QQmlJSMetaPropertyBinding(QQmlJS::SourceLocation location, const QString &propName)
: m_sourceLocation(location), m_propertyName(propName)
@@ -643,8 +671,9 @@ public:
m_bindingContent = Content::StringLiteral { value.toString() };
}
- void setScriptBinding(QQmlJSMetaMethod::RelativeFunctionIndex value, ScriptBindingKind kind,
- ScriptBindingValueType valueType = ScriptValue_Unknown)
+ void
+ setScriptBinding(QQmlJSMetaMethod::RelativeFunctionIndex value, ScriptBindingKind kind,
+ ScriptBindingValueType valueType = ScriptBindingValueType::ScriptValue_Unknown)
{
ensureSetBindingTypeOnce();
m_bindingContent = Content::Script { value, kind, valueType };
@@ -717,8 +746,6 @@ public:
m_bindingContent = Content::ValueSource { typeName, type };
}
- QString literalTypeName() const;
-
// ### TODO: here and below: Introduce an allowConversion parameter, if yes, enable conversions e.g. bool -> number?
bool boolValue() const;
@@ -745,7 +772,7 @@ public:
if (auto *script = std::get_if<Content::Script>(&m_bindingContent))
return script->kind;
// warn
- return ScriptBindingKind::Script_Invalid;
+ return ScriptBindingKind::Invalid;
}
ScriptBindingValueType scriptValueType() const
@@ -852,7 +879,7 @@ public:
}
};
-struct Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSMetaSignalHandler
+struct Q_QMLCOMPILER_EXPORT QQmlJSMetaSignalHandler
{
QStringList signalParameters;
bool isMultiline;
diff --git a/src/qmlcompiler/qqmljsoptimizations.cpp b/src/qmlcompiler/qqmljsoptimizations.cpp
new file mode 100644
index 0000000000..d542331be6
--- /dev/null
+++ b/src/qmlcompiler/qqmljsoptimizations.cpp
@@ -0,0 +1,503 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "qqmljsoptimizations_p.h"
+#include "qqmljsbasicblocks_p.h"
+#include "qqmljsutils_p.h"
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::Literals::StringLiterals;
+
+QQmlJSCompilePass::BlocksAndAnnotations QQmlJSOptimizations::run(const Function *function,
+ QQmlJS::DiagnosticMessage *error)
+{
+ m_function = function;
+ m_error = error;
+
+ populateBasicBlocks();
+ populateReaderLocations();
+ adjustTypes();
+
+ return { std::move(m_basicBlocks), std::move(m_annotations) };
+}
+
+struct PendingBlock
+{
+ QQmlJSOptimizations::Conversions conversions;
+ int start = -1;
+ bool registerActive = false;
+};
+
+template<typename ContainerA, typename ContainerB>
+static bool containsAny(const ContainerA &container, const ContainerB &elements)
+{
+ for (const auto &element : elements) {
+ if (container.contains(element))
+ return true;
+ }
+ return false;
+}
+
+template<class Key, class T, class Compare = std::less<Key>,
+ class KeyContainer = QList<Key>, class MappedContainer = QList<T>>
+class NewFlatMap
+{
+public:
+ using OriginalFlatMap = QFlatMap<Key, T, Compare, KeyContainer, MappedContainer>;
+
+ void appendOrdered(const typename OriginalFlatMap::iterator &i)
+ {
+ keys.append(i.key());
+ values.append(i.value());
+ }
+
+ OriginalFlatMap take()
+ {
+ OriginalFlatMap result(Qt::OrderedUniqueRange, std::move(keys), std::move(values));
+ keys.clear();
+ values.clear();
+ return result;
+ }
+
+private:
+ typename OriginalFlatMap::key_container_type keys;
+ typename OriginalFlatMap::mapped_container_type values;
+};
+
+void QQmlJSOptimizations::populateReaderLocations()
+{
+ using NewInstructionAnnotations = NewFlatMap<int, InstructionAnnotation>;
+
+ bool erasedReaders = false;
+ auto eraseDeadStore = [&](const InstructionAnnotations::iterator &it) {
+ auto reader = m_readerLocations.find(it.key());
+ if (reader != m_readerLocations.end()
+ && (reader->typeReaders.isEmpty() || reader->registerReadersAndConversions.isEmpty())) {
+
+ if (it->second.isRename) {
+ // If it's a rename, it doesn't "own" its output type. The type may
+ // still be read elsewhere, even if this register isn't. However, we're
+ // not interested in the variant or any other details of the register.
+ // Therefore just delete it.
+ it->second.changedRegisterIndex = InvalidRegister;
+ it->second.changedRegister = QQmlJSRegisterContent();
+ } else {
+ // void the output, rather than deleting it. We still need its variant.
+ const bool adjusted = m_typeResolver->adjustTrackedType(
+ it->second.changedRegister.containedType(),
+ m_typeResolver->voidType());
+ Q_ASSERT(adjusted); // Can always convert to void
+ }
+ m_readerLocations.erase(reader);
+
+ // If it's not a label and has no side effects, we can drop the instruction.
+ if (!it->second.hasSideEffects) {
+ if (!it->second.readRegisters.isEmpty()) {
+ it->second.readRegisters.clear();
+ erasedReaders = true;
+ }
+ if (m_basicBlocks.find(it.key()) == m_basicBlocks.end())
+ return true;
+ }
+ }
+ return false;
+ };
+
+ NewInstructionAnnotations newAnnotations;
+ for (auto writeIt = m_annotations.begin(), writeEnd = m_annotations.end();
+ writeIt != writeEnd; ++writeIt) {
+ const int writtenRegister = writeIt->second.changedRegisterIndex;
+ if (writtenRegister == InvalidRegister) {
+ newAnnotations.appendOrdered(writeIt);
+ continue;
+ }
+
+ RegisterAccess &access = m_readerLocations[writeIt.key()];
+ access.trackedRegister = writtenRegister;
+ if (writeIt->second.changedRegister.isConversion()) {
+ // If it's a conversion, we have to check for all readers of the conversion origins.
+ // This happens at jump targets where different types are merged. A StoreReg or similar
+ // instruction must be optimized out if none of the types it can hold is read anymore.
+ access.trackedTypes = writeIt->second.changedRegister.conversionOrigins();
+ } else {
+ access.trackedTypes.append(
+ m_typeResolver->trackedContainedType(writeIt->second.changedRegister));
+ Q_ASSERT(!access.trackedTypes.last().isNull());
+ }
+
+ auto blockIt = QQmlJSBasicBlocks::basicBlockForInstruction(m_basicBlocks, writeIt.key());
+ QList<PendingBlock> blocks = { { {}, blockIt->first, true } };
+ QHash<int, PendingBlock> processedBlocks;
+ bool isFirstBlock = true;
+
+ while (!blocks.isEmpty()) {
+ const PendingBlock block = blocks.takeLast();
+
+ // We can re-enter the first block from the beginning.
+ // We will then find any reads before the write we're currently examining.
+ if (!isFirstBlock)
+ processedBlocks.insert(block.start, block);
+
+ auto nextBlock = m_basicBlocks.find(block.start);
+ auto currentBlock = nextBlock++;
+ bool registerActive = block.registerActive;
+ Conversions conversions = block.conversions;
+
+ const auto blockEnd = (nextBlock == m_basicBlocks.end())
+ ? m_annotations.end()
+ : m_annotations.find(nextBlock->first);
+
+ auto blockInstr = isFirstBlock
+ ? (writeIt + 1)
+ : m_annotations.find(currentBlock->first);
+ for (; blockInstr != blockEnd; ++blockInstr) {
+ if (registerActive
+ && blockInstr->second.typeConversions.contains(writtenRegister)) {
+ conversions.insert(blockInstr.key());
+ }
+
+ for (auto readIt = blockInstr->second.readRegisters.constBegin(),
+ end = blockInstr->second.readRegisters.constEnd();
+ readIt != end; ++readIt) {
+ if (!blockInstr->second.isRename && containsAny(
+ readIt->second.content.conversionOrigins(), access.trackedTypes)) {
+ Q_ASSERT(readIt->second.content.isConversion());
+ Q_ASSERT(readIt->second.content.conversionResult());
+ access.typeReaders[blockInstr.key()]
+ = readIt->second.content.conversionResult();
+ }
+ if (registerActive && readIt->first == writtenRegister)
+ access.registerReadersAndConversions[blockInstr.key()] = conversions;
+ }
+
+ if (blockInstr->second.changedRegisterIndex == writtenRegister) {
+ conversions.clear();
+ registerActive = false;
+ }
+ }
+
+ auto scheduleBlock = [&](int blockStart) {
+ // If we find that an already processed block has the register activated by this jump,
+ // we need to re-evaluate it. We also need to propagate any newly found conversions.
+ const auto processed = processedBlocks.find(blockStart);
+ if (processed == processedBlocks.end()) {
+ blocks.append({conversions, blockStart, registerActive});
+ } else if (registerActive && !processed->registerActive) {
+ blocks.append({conversions, blockStart, registerActive});
+ } else {
+
+ // TODO: Use unite() once it is fixed.
+ // We don't use unite() here since it would be more expensive. unite()
+ // effectively loops on only insert() and insert() does a number of checks
+ // each time. We trade those checks for calculating the hash twice on each
+ // iteration. Calculating the hash is very cheap for integers.
+ Conversions merged = processed->conversions;
+ for (const int conversion : std::as_const(conversions)) {
+ if (!merged.contains(conversion))
+ merged.insert(conversion);
+ }
+
+ if (merged.size() > processed->conversions.size())
+ blocks.append({std::move(merged), blockStart, registerActive});
+ }
+ };
+
+ if (!currentBlock->second.jumpIsUnconditional && nextBlock != m_basicBlocks.end())
+ scheduleBlock(nextBlock->first);
+
+ const int jumpTarget = currentBlock->second.jumpTarget;
+ if (jumpTarget != -1)
+ scheduleBlock(jumpTarget);
+
+ if (isFirstBlock)
+ isFirstBlock = false;
+ }
+
+ if (!eraseDeadStore(writeIt))
+ newAnnotations.appendOrdered(writeIt);
+ }
+ m_annotations = newAnnotations.take();
+
+ while (erasedReaders) {
+ erasedReaders = false;
+
+ for (auto it = m_annotations.begin(), end = m_annotations.end(); it != end; ++it) {
+ InstructionAnnotation &instruction = it->second;
+ if (instruction.changedRegisterIndex < InvalidRegister) {
+ newAnnotations.appendOrdered(it);
+ continue;
+ }
+
+ auto readers = m_readerLocations.find(it.key());
+ if (readers != m_readerLocations.end()) {
+ for (auto typeIt = readers->typeReaders.begin();
+ typeIt != readers->typeReaders.end();) {
+ if (m_annotations.contains(typeIt.key()))
+ ++typeIt;
+ else
+ typeIt = readers->typeReaders.erase(typeIt);
+ }
+
+ for (auto registerIt = readers->registerReadersAndConversions.begin();
+ registerIt != readers->registerReadersAndConversions.end();) {
+ if (m_annotations.contains(registerIt.key()))
+ ++registerIt;
+ else
+ registerIt = readers->registerReadersAndConversions.erase(registerIt);
+ }
+ }
+
+ if (!eraseDeadStore(it))
+ newAnnotations.appendOrdered(it);
+ }
+
+ m_annotations = newAnnotations.take();
+ }
+}
+
+bool QQmlJSOptimizations::canMove(int instructionOffset,
+ const QQmlJSOptimizations::RegisterAccess &access) const
+{
+ if (access.registerReadersAndConversions.size() != 1)
+ return false;
+ return QQmlJSBasicBlocks::basicBlockForInstruction(m_basicBlocks, instructionOffset)
+ == QQmlJSBasicBlocks::basicBlockForInstruction(m_basicBlocks, access.registerReadersAndConversions.begin().key());
+}
+
+QList<QQmlJSCompilePass::ObjectOrArrayDefinition>
+QQmlJSBasicBlocks::objectAndArrayDefinitions() const
+{
+ return m_objectAndArrayDefinitions;
+}
+
+static QString adjustErrorMessage(
+ const QQmlJSScope::ConstPtr &origin, const QQmlJSScope::ConstPtr &conversion) {
+ return QLatin1String("Cannot convert from ")
+ + origin->internalName() + QLatin1String(" to ") + conversion->internalName();
+}
+
+static QString adjustErrorMessage(
+ const QQmlJSScope::ConstPtr &origin, const QList<QQmlJSScope::ConstPtr> &conversions) {
+ if (conversions.size() == 1)
+ return adjustErrorMessage(origin, conversions[0]);
+
+ QString types;
+ for (const QQmlJSScope::ConstPtr &type : conversions) {
+ if (!types.isEmpty())
+ types += QLatin1String(", ");
+ types += type->internalName();
+ }
+ return QLatin1String("Cannot convert from ")
+ + origin->internalName() + QLatin1String(" to union of ") + types;
+}
+
+void QQmlJSOptimizations::adjustTypes()
+{
+ using NewVirtualRegisters = NewFlatMap<int, VirtualRegister>;
+
+ QHash<int, QList<int>> liveConversions;
+ QHash<int, QList<int>> movableReads;
+
+ const auto handleRegisterReadersAndConversions
+ = [&](QHash<int, RegisterAccess>::const_iterator it) {
+ for (auto conversions = it->registerReadersAndConversions.constBegin(),
+ end = it->registerReadersAndConversions.constEnd(); conversions != end;
+ ++conversions) {
+ if (conversions->isEmpty() && canMove(it.key(), it.value()))
+ movableReads[conversions.key()].append(it->trackedRegister);
+ for (int conversion : *conversions)
+ liveConversions[conversion].append(it->trackedRegister);
+ }
+ };
+
+ // Handle the array definitions first.
+ // Changing the array type changes the expected element types.
+ auto adjustArray = [&](int instructionOffset, int mode) {
+ auto it = m_readerLocations.find(instructionOffset);
+ if (it == m_readerLocations.end())
+ return;
+
+ const InstructionAnnotation &annotation = m_annotations[instructionOffset];
+ if (annotation.readRegisters.isEmpty())
+ return;
+
+ Q_ASSERT(it->trackedTypes.size() == 1);
+ Q_ASSERT(it->trackedTypes[0] == annotation.changedRegister.containedType());
+
+ if (it->trackedTypes[0]->accessSemantics() != QQmlJSScope::AccessSemantics::Sequence)
+ return; // Constructed something else.
+
+ if (!m_typeResolver->adjustTrackedType(it->trackedTypes[0], it->typeReaders.values()))
+ setError(adjustErrorMessage(it->trackedTypes[0], it->typeReaders.values()));
+
+ // Now we don't adjust the type we store, but rather the type we expect to read. We
+ // can do this because we've tracked the read type when we defined the array in
+ // QQmlJSTypePropagator.
+ if (QQmlJSScope::ConstPtr valueType = it->trackedTypes[0]->valueType()) {
+ const QQmlJSRegisterContent content = annotation.readRegisters.begin().value().content;
+ const QQmlJSScope::ConstPtr contained = content.containedType();
+
+ // If it's the 1-arg Array ctor, and the argument is a number, that's special.
+ if (mode != ObjectOrArrayDefinition::ArrayConstruct1ArgId
+ || !m_typeResolver->equals(contained, m_typeResolver->realType())) {
+ if (!m_typeResolver->adjustTrackedType(contained, valueType))
+ setError(adjustErrorMessage(contained, valueType));
+ }
+ }
+
+ handleRegisterReadersAndConversions(it);
+ m_readerLocations.erase(it);
+ };
+
+ // Handle the object definitions.
+ // Changing the object type changes the expected property types.
+ const auto adjustObject = [&](const ObjectOrArrayDefinition &object) {
+ auto it = m_readerLocations.find(object.instructionOffset);
+ if (it == m_readerLocations.end())
+ return;
+
+ const InstructionAnnotation &annotation = m_annotations[object.instructionOffset];
+
+ Q_ASSERT(it->trackedTypes.size() == 1);
+ QQmlJSScope::ConstPtr resultType = it->trackedTypes[0];
+
+ Q_ASSERT(resultType == annotation.changedRegister.containedType());
+ Q_ASSERT(!annotation.readRegisters.isEmpty());
+
+ if (!m_typeResolver->adjustTrackedType(resultType, it->typeReaders.values()))
+ setError(adjustErrorMessage(resultType, it->typeReaders.values()));
+
+ if (m_typeResolver->equals(resultType, m_typeResolver->varType())
+ || m_typeResolver->equals(resultType, m_typeResolver->variantMapType())) {
+ // It's all variant anyway
+ return;
+ }
+
+ const int classSize = m_jsUnitGenerator->jsClassSize(object.internalClassId);
+ Q_ASSERT(object.argc >= classSize);
+
+ for (int i = 0; i < classSize; ++i) {
+ // Now we don't adjust the type we store, but rather the types we expect to read. We
+ // can do this because we've tracked the read types when we defined the object in
+ // QQmlJSTypePropagator.
+
+ const QString propName = m_jsUnitGenerator->jsClassMember(object.internalClassId, i);
+ const QQmlJSMetaProperty property = resultType->property(propName);
+ if (!property.isValid()) {
+ setError(resultType->internalName() + QLatin1String(" has no property called ")
+ + propName);
+ continue;
+ }
+ const QQmlJSScope::ConstPtr propType = property.type();
+ if (propType.isNull()) {
+ setError(QLatin1String("Cannot resolve type of property ") + propName);
+ continue;
+ }
+ const QQmlJSRegisterContent content = annotation.readRegisters[object.argv + i].content;
+ const QQmlJSScope::ConstPtr contained = content.containedType();
+ if (!m_typeResolver->adjustTrackedType(contained, propType))
+ setError(adjustErrorMessage(contained, propType));
+ }
+
+ // The others cannot be adjusted. We don't know their names, yet.
+ // But we might still be able to use the variants.
+ };
+
+ // Iterate in reverse so that we can have nested lists and objects and the types are propagated
+ // from the outer lists/objects to the inner ones.
+ for (auto it = m_objectAndArrayDefinitions.crbegin(), end = m_objectAndArrayDefinitions.crend();
+ it != end; ++it) {
+ switch (it->internalClassId) {
+ case ObjectOrArrayDefinition::ArrayClassId:
+ case ObjectOrArrayDefinition::ArrayConstruct1ArgId:
+ adjustArray(it->instructionOffset, it->internalClassId);
+ break;
+ default:
+ adjustObject(*it);
+ break;
+ }
+ }
+
+ for (auto it = m_readerLocations.begin(), end = m_readerLocations.end(); it != end; ++it) {
+ handleRegisterReadersAndConversions(it);
+
+ // There is always one first occurrence of any tracked type. Conversions don't change
+ // the type.
+ if (it->trackedTypes.size() != 1)
+ continue;
+
+ // Don't adjust renamed values. We only adjust the originals.
+ const int writeLocation = it.key();
+ if (writeLocation >= 0 && m_annotations[writeLocation].isRename)
+ continue;
+
+ if (!m_typeResolver->adjustTrackedType(it->trackedTypes[0], it->typeReaders.values()))
+ setError(adjustErrorMessage(it->trackedTypes[0], it->typeReaders.values()));
+ }
+
+
+ NewVirtualRegisters newRegisters;
+ for (auto i = m_annotations.begin(), iEnd = m_annotations.end(); i != iEnd; ++i) {
+ for (auto conversion = i->second.typeConversions.begin(),
+ conversionEnd = i->second.typeConversions.end(); conversion != conversionEnd;
+ ++conversion) {
+ if (!liveConversions[i.key()].contains(conversion.key()))
+ continue;
+
+ QQmlJSScope::ConstPtr newResult;
+ const auto content = conversion->second.content;
+ if (content.isConversion()) {
+ QQmlJSScope::ConstPtr conversionResult = content.conversionResult();
+ const auto conversionOrigins = content.conversionOrigins();
+ for (const auto &origin : conversionOrigins)
+ newResult = m_typeResolver->merge(newResult, origin);
+ if (!m_typeResolver->adjustTrackedType(conversionResult, newResult))
+ setError(adjustErrorMessage(conversionResult, newResult));
+ }
+ newRegisters.appendOrdered(conversion);
+ }
+ i->second.typeConversions = newRegisters.take();
+
+ for (int movable : std::as_const(movableReads[i.key()]))
+ i->second.readRegisters[movable].canMove = true;
+ }
+}
+
+void QQmlJSOptimizations::populateBasicBlocks()
+{
+ for (auto blockNext = m_basicBlocks.begin(), blockEnd = m_basicBlocks.end();
+ blockNext != blockEnd;) {
+
+ const auto blockIt = blockNext++;
+ BasicBlock &block = blockIt->second;
+ QList<QQmlJSScope::ConstPtr> writtenTypes;
+ QList<int> writtenRegisters;
+
+ const auto instrEnd = (blockNext == blockEnd) ? m_annotations.end()
+ : m_annotations.find(blockNext->first);
+ for (auto instrIt = m_annotations.find(blockIt->first); instrIt != instrEnd; ++instrIt) {
+ const InstructionAnnotation &instruction = instrIt->second;
+ for (auto it = instruction.readRegisters.begin(), end = instruction.readRegisters.end();
+ it != end; ++it) {
+ Q_ASSERT(instruction.isRename || it->second.content.isConversion());
+ if (!writtenRegisters.contains(it->first))
+ block.readRegisters.append(it->first);
+ }
+
+ // If it's just a renaming, the type has existed in a different register before.
+ if (instruction.changedRegisterIndex != InvalidRegister) {
+ if (!instruction.isRename) {
+ writtenTypes.append(m_typeResolver->trackedContainedType(
+ instruction.changedRegister));
+ }
+ writtenRegisters.append(instruction.changedRegisterIndex);
+ }
+ }
+
+ QQmlJSUtils::deduplicate(block.readRegisters);
+ }
+}
+
+
+QT_END_NAMESPACE
diff --git a/src/qmlcompiler/qqmljsoptimizations_p.h b/src/qmlcompiler/qqmljsoptimizations_p.h
new file mode 100644
index 0000000000..0d7091ab49
--- /dev/null
+++ b/src/qmlcompiler/qqmljsoptimizations_p.h
@@ -0,0 +1,65 @@
+// 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 QQMLJSOPTIMIZATIONS_P_H
+#define QQMLJSOPTIMIZATIONS_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#include <private/qqmljscompilepass_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class Q_QMLCOMPILER_EXPORT QQmlJSOptimizations : public QQmlJSCompilePass
+{
+public:
+ using Conversions = QSet<int>;
+
+ QQmlJSOptimizations(const QV4::Compiler::JSUnitGenerator *unitGenerator,
+ const QQmlJSTypeResolver *typeResolver, QQmlJSLogger *logger,
+ BasicBlocks basicBlocks, InstructionAnnotations annotations,
+ QList<ObjectOrArrayDefinition> objectAndArrayDefinitions)
+ : QQmlJSCompilePass(unitGenerator, typeResolver, logger, basicBlocks, annotations),
+ m_objectAndArrayDefinitions{ objectAndArrayDefinitions }
+ {
+ }
+
+ ~QQmlJSOptimizations() = default;
+
+ BlocksAndAnnotations run(const Function *function, QQmlJS::DiagnosticMessage *error);
+
+private:
+ struct RegisterAccess
+ {
+ QList<QQmlJSScope::ConstPtr> trackedTypes;
+ QHash<int, QQmlJSScope::ConstPtr> typeReaders;
+ QHash<int, Conversions> registerReadersAndConversions;
+ int trackedRegister;
+ };
+
+ QV4::Moth::ByteCodeHandler::Verdict startInstruction(QV4::Moth::Instr::Type) override
+ {
+ return ProcessInstruction;
+ }
+ void endInstruction(QV4::Moth::Instr::Type) override { }
+
+ void populateBasicBlocks();
+ void populateReaderLocations();
+ void adjustTypes();
+ bool canMove(int instructionOffset, const RegisterAccess &access) const;
+
+ QHash<int, RegisterAccess> m_readerLocations;
+ QList<ObjectOrArrayDefinition> m_objectAndArrayDefinitions;
+};
+
+QT_END_NAMESPACE
+
+#endif // QQMLJSOPTIMIZATIONS_P_H
diff --git a/src/qmlcompiler/qqmljsregistercontent.cpp b/src/qmlcompiler/qqmljsregistercontent.cpp
index 1573f889a1..3efb738e46 100644
--- a/src/qmlcompiler/qqmljsregistercontent.cpp
+++ b/src/qmlcompiler/qqmljsregistercontent.cpp
@@ -2,7 +2,6 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "qqmljsregistercontent_p.h"
-#include "qqmljstyperesolver_p.h"
QT_BEGIN_NAMESPACE
@@ -10,10 +9,9 @@ using namespace Qt::StringLiterals;
QString QQmlJSRegisterContent::descriptiveName() const
{
- if (m_storedType.isNull())
+ if (m_storedType.isNull() && containedType().isNull())
return u"(invalid type)"_s;
- QString result = m_storedType->internalName() + u" of "_s;
const auto scope = [this]() -> QString {
if (m_scope.isNull())
return u"(invalid type)::"_s;
@@ -25,46 +23,81 @@ QString QQmlJSRegisterContent::descriptiveName() const
+ u"::"_s;
};
+ QString result;
switch (m_content.index()) {
- case Type:
- return result + std::get<QQmlJSScope::ConstPtr>(m_content)->internalName();
+ case Type: {
+ const QQmlJSScope::ConstPtr contained = type();
+ result += contained->internalName();
+ if (m_storedType && m_storedType->internalName() != contained->internalName())
+ result += u" stored as "_s + m_storedType->internalName();
+ return result;
+ }
case Property: {
- const QQmlJSMetaProperty prop = std::get<QQmlJSMetaProperty>(m_content);
- return result + scope() + prop.propertyName() + u" with type "_s + prop.typeName();
+ const QQmlJSMetaProperty prop = property();
+ result += scope() + prop.propertyName() + u" with type "_s + prop.typeName();
+ if (m_storedType && m_storedType->internalName() != prop.typeName())
+ result += u" (stored as "_s + m_storedType->internalName() + u")";
+ return result;
}
case Method: {
- const auto methods = std::get<QList<QQmlJSMetaMethod>>(m_content);
+ const auto methods = method();
if (methods.isEmpty())
- return result + scope() + u"(unknown method)"_s;
+ result = scope() + u"(unknown method)"_s;
else
- return result + scope() + methods[0].methodName() + u"(...)"_s;
+ result = scope() + methods[0].methodName() + u"(...)"_s;
+ if (m_storedType)
+ return result + u" (stored as "_s + m_storedType->internalName() + u")";
+ return result;
}
case Enum: {
- const auto e = std::get<std::pair<QQmlJSMetaEnum, QString>>(m_content);
- if (e.second.isEmpty())
- return result + scope() + e.first.name();
+ const QString enumName = enumeration().name();
+ const QString memberName = enumMember();
+ if (memberName.isEmpty())
+ result = scope() + enumName;
else
- return result + scope() + e.first.name() + u"::"_s + e.second;
+ result = scope() + enumName + u"::"_s + memberName;
+ if (m_storedType)
+ return result + u" (stored as "_s + m_storedType->internalName() + u")";
+ return result;
}
case ImportNamespace: {
- return u"import namespace %1"_s.arg(std::get<uint>(m_content));
+ return u"import namespace %1"_s.arg(importNamespace());
}
case Conversion: {
- return u"conversion to %1"_s.arg(
- std::get<ConvertedTypes>(m_content).result->internalName());
+ return u"conversion to %1"_s.arg(conversionResult()->internalName());
}
}
+
Q_UNREACHABLE_RETURN(result + u"wat?"_s);
}
+QString QQmlJSRegisterContent::containedTypeName() const
+{
+ QQmlJSScope::ConstPtr type;
+
+ // Use the type proper instead of the attached type
+ switch (variant()) {
+ case QQmlJSRegisterContent::ScopeAttached:
+ case QQmlJSRegisterContent::MetaType:
+ type = scopeType();
+ break;
+ default:
+ type = containedType();
+ break;
+ }
+
+ return QQmlJSScope::prettyName(
+ type->internalName().isEmpty() ? type->baseTypeName() : type->internalName());
+}
+
bool QQmlJSRegisterContent::isList() const
{
switch (m_content.index()) {
case Type:
- return std::get<QQmlJSScope::ConstPtr>(m_content)->accessSemantics()
+ return std::get<std::pair<QQmlJSScope::ConstPtr, int>>(m_content).first->accessSemantics()
== QQmlJSScope::AccessSemantics::Sequence;
case Property:
- return std::get<QQmlJSMetaProperty>(m_content).type()->accessSemantics()
+ return std::get<PropertyLookup>(m_content).property.type()->accessSemantics()
== QQmlJSScope::AccessSemantics::Sequence;
case Conversion:
return std::get<ConvertedTypes>(m_content).result->accessSemantics()
@@ -78,7 +111,7 @@ bool QQmlJSRegisterContent::isWritable() const
{
switch (m_content.index()) {
case Property:
- return std::get<QQmlJSMetaProperty>(m_content).isWritable();
+ return std::get<PropertyLookup>(m_content).property.isWritable();
// TODO: What can we actually write?
default:
@@ -88,65 +121,78 @@ bool QQmlJSRegisterContent::isWritable() const
return true;
}
-QQmlJSRegisterContent QQmlJSRegisterContent::create(const QQmlJSScope::ConstPtr &storedType,
- const QQmlJSScope::ConstPtr &type,
- QQmlJSRegisterContent::ContentVariant variant,
- const QQmlJSScope::ConstPtr &scope)
+QQmlJSScope::ConstPtr QQmlJSRegisterContent::containedType() const
+{
+ if (isType())
+ return type();
+ if (isProperty())
+ return property().type();
+ if (isEnumeration())
+ return enumeration().type();
+ if (isMethod())
+ return methodType();
+ if (isImportNamespace())
+ return importNamespaceType();
+ if (isConversion())
+ return conversionResult();
+
+ Q_UNREACHABLE_RETURN({});
+}
+
+QQmlJSRegisterContent QQmlJSRegisterContent::create(
+ const QQmlJSScope::ConstPtr &type, int resultLookupIndex,
+ QQmlJSRegisterContent::ContentVariant variant, const QQmlJSScope::ConstPtr &scope)
{
- QQmlJSRegisterContent result(storedType, scope, variant);
- result.m_content = type;
+ QQmlJSRegisterContent result(scope, variant);
+ result.m_content = std::make_pair(type, resultLookupIndex);
return result;
}
-QQmlJSRegisterContent QQmlJSRegisterContent::create(const QQmlJSScope::ConstPtr &storedType,
- const QQmlJSMetaProperty &property,
- QQmlJSRegisterContent::ContentVariant variant,
- const QQmlJSScope::ConstPtr &scope)
+QQmlJSRegisterContent QQmlJSRegisterContent::create(
+ const QQmlJSMetaProperty &property, int baseLookupIndex, int resultLookupIndex,
+ QQmlJSRegisterContent::ContentVariant variant, const QQmlJSScope::ConstPtr &scope)
{
- QQmlJSRegisterContent result(storedType, scope, variant);
- result.m_content = property;
+ QQmlJSRegisterContent result(scope, variant);
+ result.m_content = PropertyLookup { property, baseLookupIndex, resultLookupIndex};
return result;
}
-QQmlJSRegisterContent QQmlJSRegisterContent::create(const QQmlJSScope::ConstPtr &storedType,
- const QQmlJSMetaEnum &enumeration,
- const QString &enumMember,
- QQmlJSRegisterContent::ContentVariant variant,
- const QQmlJSScope::ConstPtr &scope)
+QQmlJSRegisterContent QQmlJSRegisterContent::create(
+ const QQmlJSMetaEnum &enumeration, const QString &enumMember,
+ QQmlJSRegisterContent::ContentVariant variant, const QQmlJSScope::ConstPtr &scope)
{
- QQmlJSRegisterContent result(storedType, scope, variant);
+ QQmlJSRegisterContent result(scope, variant);
result.m_content = std::make_pair(enumeration, enumMember);
return result;
}
-QQmlJSRegisterContent QQmlJSRegisterContent::create(const QQmlJSScope::ConstPtr &storedType,
- const QList<QQmlJSMetaMethod> &methods,
- QQmlJSRegisterContent::ContentVariant variant,
- const QQmlJSScope::ConstPtr &scope)
+QQmlJSRegisterContent QQmlJSRegisterContent::create(
+ const QList<QQmlJSMetaMethod> &methods, const QQmlJSScope::ConstPtr &methodType,
+ QQmlJSRegisterContent::ContentVariant variant, const QQmlJSScope::ConstPtr &scope)
{
- QQmlJSRegisterContent result(storedType, scope, variant);
- result.m_content = methods;
+ // Methods can only be stored in QJSValue.
+ Q_ASSERT(methodType->internalName() == u"QJSValue"_s);
+ QQmlJSRegisterContent result(scope, variant);
+ result.m_content = std::make_pair(methods, methodType);
return result;
}
-QQmlJSRegisterContent QQmlJSRegisterContent::create(const QQmlJSScope::ConstPtr &storedType,
- uint importNamespaceStringId,
- QQmlJSRegisterContent::ContentVariant variant,
- const QQmlJSScope::ConstPtr &scope)
+QQmlJSRegisterContent QQmlJSRegisterContent::create(
+ uint importNamespaceStringId, const QQmlJSScope::ConstPtr &importNamespaceType,
+ QQmlJSRegisterContent::ContentVariant variant, const QQmlJSScope::ConstPtr &scope)
{
- QQmlJSRegisterContent result(storedType, scope, variant);
- result.m_content = importNamespaceStringId;
+ QQmlJSRegisterContent result(scope, variant);
+ result.m_content = std::make_pair(importNamespaceStringId, importNamespaceType);
return result;
}
-QQmlJSRegisterContent QQmlJSRegisterContent::create(const QQmlJSScope::ConstPtr &storedType,
- const QList<QQmlJSScope::ConstPtr> origins,
- const QQmlJSScope::ConstPtr &conversion,
- ContentVariant variant,
- const QQmlJSScope::ConstPtr &scope)
+QQmlJSRegisterContent QQmlJSRegisterContent::create(
+ const QList<QQmlJSScope::ConstPtr> &origins, const QQmlJSScope::ConstPtr &conversion,
+ const QQmlJSScope::ConstPtr &conversionScope, ContentVariant variant,
+ const QQmlJSScope::ConstPtr &scope)
{
- QQmlJSRegisterContent result(storedType, scope, variant);
- result.m_content = ConvertedTypes { origins, conversion };
+ QQmlJSRegisterContent result(scope, variant);
+ result.m_content = ConvertedTypes { origins, conversion, conversionScope };
return result;
}
diff --git a/src/qmlcompiler/qqmljsregistercontent_p.h b/src/qmlcompiler/qqmljsregistercontent_p.h
index 1ca44fd0c3..ccc647d901 100644
--- a/src/qmlcompiler/qqmljsregistercontent_p.h
+++ b/src/qmlcompiler/qqmljsregistercontent_p.h
@@ -22,7 +22,7 @@
QT_BEGIN_NAMESPACE
-class Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSRegisterContent
+class Q_QMLCOMPILER_EXPORT QQmlJSRegisterContent
{
public:
enum ContentVariant {
@@ -34,7 +34,7 @@ public:
JavaScriptGlobal,
JavaScriptObject,
JavaScriptScopeProperty,
- JavaScriptObjectProperty,
+ GenericObjectProperty, // Can be JSObject property or QVariantMap
ScopeProperty,
ScopeMethod,
@@ -56,14 +56,18 @@ public:
JavaScriptReturnValue,
ListValue,
+ ListIterator,
Builtin,
Unknown,
};
+ enum { InvalidLookupIndex = -1 };
+
QQmlJSRegisterContent() = default;
- bool isValid() const { return !m_storedType.isNull(); }
+ bool isValid() const { return !containedType().isNull(); }
QString descriptiveName() const;
+ QString containedTypeName() const;
friend bool operator==(const QQmlJSRegisterContent &a, const QQmlJSRegisterContent &b)
{
@@ -87,10 +91,32 @@ public:
bool isWritable() const;
QQmlJSScope::ConstPtr storedType() const { return m_storedType; }
+ QQmlJSScope::ConstPtr containedType() const;
QQmlJSScope::ConstPtr scopeType() const { return m_scope; }
- QQmlJSScope::ConstPtr type() const { return std::get<QQmlJSScope::ConstPtr>(m_content); }
- QQmlJSMetaProperty property() const { return std::get<QQmlJSMetaProperty>(m_content); }
+ QQmlJSScope::ConstPtr type() const
+ {
+ return std::get<std::pair<QQmlJSScope::ConstPtr, int>>(m_content).first;
+ }
+ QQmlJSMetaProperty property() const
+ {
+ return std::get<PropertyLookup>(m_content).property;
+ }
+ int baseLookupIndex() const
+ {
+ return std::get<PropertyLookup>(m_content).baseLookupIndex;
+ }
+ int resultLookupIndex() const
+ {
+ switch (m_content.index()) {
+ case Type:
+ return std::get<std::pair<QQmlJSScope::ConstPtr, int>>(m_content).second;
+ case Property:
+ return std::get<PropertyLookup>(m_content).resultLookupIndex;
+ default:
+ return InvalidLookupIndex;
+ }
+ }
QQmlJSMetaEnum enumeration() const
{
return std::get<std::pair<QQmlJSMetaEnum, QString>>(m_content).first;
@@ -99,14 +125,35 @@ public:
{
return std::get<std::pair<QQmlJSMetaEnum, QString>>(m_content).second;
}
- QList<QQmlJSMetaMethod> method() const { return std::get<QList<QQmlJSMetaMethod>>(m_content); }
- uint importNamespace() const { return std::get<uint>(m_content); }
+ QList<QQmlJSMetaMethod> method() const
+ {
+ return std::get<std::pair<QList<QQmlJSMetaMethod>, QQmlJSScope::ConstPtr>>(
+ m_content).first;
+ }
+ QQmlJSScope::ConstPtr methodType() const
+ {
+ return std::get<std::pair<QList<QQmlJSMetaMethod>, QQmlJSScope::ConstPtr>>(
+ m_content).second;
+ }
+ uint importNamespace() const
+ {
+ return std::get<std::pair<uint, QQmlJSScope::ConstPtr>>(m_content).first;
+ }
+ QQmlJSScope::ConstPtr importNamespaceType() const
+ {
+ return std::get<std::pair<uint, QQmlJSScope::ConstPtr>>(m_content).second;
+ }
QQmlJSScope::ConstPtr conversionResult() const
{
return std::get<ConvertedTypes>(m_content).result;
}
+ QQmlJSScope::ConstPtr conversionResultScope() const
+ {
+ return std::get<ConvertedTypes>(m_content).resultScope;
+ }
+
QList<QQmlJSScope::ConstPtr> conversionOrigins() const
{
return std::get<ConvertedTypes>(m_content).origins;
@@ -120,16 +167,19 @@ public:
registerContent.m_scope, registerContent.m_variant);
switch (registerContent.m_content.index()) {
case Type:
- return qHash(std::get<QQmlJSScope::ConstPtr>(registerContent.m_content), seed);
+ return qHash(std::get<std::pair<QQmlJSScope::ConstPtr, int>>(registerContent.m_content),
+ seed);
case Property:
- return qHash(std::get<QQmlJSMetaProperty>(registerContent.m_content), seed);
+ return qHash(std::get<PropertyLookup>(registerContent.m_content), seed);
case Enum:
return qHash(std::get<std::pair<QQmlJSMetaEnum, QString>>(registerContent.m_content),
seed);
case Method:
- return qHash(std::get<QList<QQmlJSMetaMethod>>(registerContent.m_content), seed);
+ return qHash(std::get<std::pair<QList<QQmlJSMetaMethod>, QQmlJSScope::ConstPtr>>(
+ registerContent.m_content), seed);
case ImportNamespace:
- return qHash(std::get<uint>(registerContent.m_content), seed);
+ return qHash(std::get<std::pair<uint, QQmlJSScope::ConstPtr>>(
+ registerContent.m_content), seed);
case Conversion:
return qHash(std::get<ConvertedTypes>(registerContent.m_content), seed);
}
@@ -137,31 +187,32 @@ public:
Q_UNREACHABLE_RETURN(seed);
}
- static QQmlJSRegisterContent create(const QQmlJSScope::ConstPtr &storedType,
- const QQmlJSScope::ConstPtr &type, ContentVariant variant,
+ static QQmlJSRegisterContent create(const QQmlJSScope::ConstPtr &type,
+ int resultLookupIndex, ContentVariant variant,
const QQmlJSScope::ConstPtr &scope = {});
- static QQmlJSRegisterContent create(const QQmlJSScope::ConstPtr &storedType,
- const QQmlJSMetaProperty &property, ContentVariant variant,
+ static QQmlJSRegisterContent create(const QQmlJSMetaProperty &property,
+ int baseLookupIndex, int resultLookupIndex,
+ ContentVariant variant,
const QQmlJSScope::ConstPtr &scope);
- static QQmlJSRegisterContent create(const QQmlJSScope::ConstPtr &storedType,
- const QQmlJSMetaEnum &enumeration,
+ static QQmlJSRegisterContent create(const QQmlJSMetaEnum &enumeration,
const QString &enumMember, ContentVariant variant,
const QQmlJSScope::ConstPtr &scope);
- static QQmlJSRegisterContent create(const QQmlJSScope::ConstPtr &storedType,
- const QList<QQmlJSMetaMethod> &methods,
+ static QQmlJSRegisterContent create(const QList<QQmlJSMetaMethod> &methods,
+ const QQmlJSScope::ConstPtr &methodType,
ContentVariant variant,
const QQmlJSScope::ConstPtr &scope);
- static QQmlJSRegisterContent create(const QQmlJSScope::ConstPtr &storedType,
- uint importNamespaceStringId, ContentVariant variant,
+ static QQmlJSRegisterContent create(uint importNamespaceStringId,
+ const QQmlJSScope::ConstPtr &importNamespaceType,
+ ContentVariant variant,
const QQmlJSScope::ConstPtr &scope = {});
- static QQmlJSRegisterContent create(const QQmlJSScope::ConstPtr &storedType,
- const QList<QQmlJSScope::ConstPtr> origins,
+ static QQmlJSRegisterContent create(const QList<QQmlJSScope::ConstPtr> &origins,
const QQmlJSScope::ConstPtr &conversion,
+ const QQmlJSScope::ConstPtr &conversionScope,
ContentVariant variant,
const QQmlJSScope::ConstPtr &scope = {});
@@ -172,6 +223,14 @@ public:
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 = std::make_pair(newContainedType, result.resultLookupIndex());
+ return result;
+ }
+
private:
enum ContentKind { Type, Property, Enum, Method, ImportNamespace, Conversion };
@@ -179,15 +238,16 @@ private:
{
QList<QQmlJSScope::ConstPtr> origins;
QQmlJSScope::ConstPtr result;
+ QQmlJSScope::ConstPtr resultScope;
friend size_t qHash(const ConvertedTypes &types, size_t seed = 0)
{
- return qHashMulti(seed, types.origins, types.result);
+ return qHashMulti(seed, types.origins, types.result, types.resultScope);
}
friend bool operator==(const ConvertedTypes &a, const ConvertedTypes &b)
{
- return a.origins == b.origins && a.result == b.result;
+ return a.origins == b.origins && a.result == b.result && a.resultScope == b.resultScope;
}
friend bool operator!=(const ConvertedTypes &a, const ConvertedTypes &b)
@@ -196,18 +256,42 @@ private:
}
};
+ struct PropertyLookup
+ {
+ QQmlJSMetaProperty property;
+ int baseLookupIndex = InvalidLookupIndex;
+ int resultLookupIndex = InvalidLookupIndex;
+
+ friend size_t qHash(const PropertyLookup &property, size_t seed = 0)
+ {
+ return qHashMulti(
+ seed, property.property, property.baseLookupIndex, property.resultLookupIndex);
+ }
+
+ friend bool operator==(const PropertyLookup &a, const PropertyLookup &b)
+ {
+ return a.baseLookupIndex == b.baseLookupIndex
+ && a.resultLookupIndex == b.resultLookupIndex
+ && a.property == b.property;
+ }
+
+ friend bool operator!=(const PropertyLookup &a, const PropertyLookup &b)
+ {
+ return !(a == b);
+ }
+ };
+
using Content = std::variant<
- QQmlJSScope::ConstPtr,
- QQmlJSMetaProperty,
+ std::pair<QQmlJSScope::ConstPtr, int>,
+ PropertyLookup,
std::pair<QQmlJSMetaEnum, QString>,
- QList<QQmlJSMetaMethod>,
- uint,
+ std::pair<QList<QQmlJSMetaMethod>, QQmlJSScope::ConstPtr>,
+ std::pair<uint, QQmlJSScope::ConstPtr>,
ConvertedTypes
>;
- QQmlJSRegisterContent(const QQmlJSScope::ConstPtr &storedType,
- const QQmlJSScope::ConstPtr &scope, ContentVariant variant)
- : m_storedType(storedType), m_scope(scope), m_variant(variant)
+ QQmlJSRegisterContent(const QQmlJSScope::ConstPtr &scope, ContentVariant variant)
+ : m_scope(scope), m_variant(variant)
{
}
diff --git a/src/qmlcompiler/qqmljsresourcefilemapper_p.h b/src/qmlcompiler/qqmljsresourcefilemapper_p.h
index 618cc2edec..16fb99e82e 100644
--- a/src/qmlcompiler/qqmljsresourcefilemapper_p.h
+++ b/src/qmlcompiler/qqmljsresourcefilemapper_p.h
@@ -13,7 +13,7 @@
//
// We mean it.
-#include <private/qtqmlcompilerexports_p.h>
+#include <qtqmlcompilerexports.h>
#include <QStringList>
#include <QHash>
@@ -22,7 +22,7 @@
QT_BEGIN_NAMESPACE
-struct Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSResourceFileMapper
+struct Q_QMLCOMPILER_EXPORT QQmlJSResourceFileMapper
{
struct Entry
{
diff --git a/src/qmlcompiler/qqmljsscope.cpp b/src/qmlcompiler/qqmljsscope.cpp
index e3d8bafeb3..535134072f 100644
--- a/src/qmlcompiler/qqmljsscope.cpp
+++ b/src/qmlcompiler/qqmljsscope.cpp
@@ -1,10 +1,13 @@
// Copyright (C) 2019 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+#include "qqmljscontextualtypes_p.h"
#include "qqmljsscope_p.h"
#include "qqmljstypereader_p.h"
#include "qqmljsimporter_p.h"
#include "qqmljsutils_p.h"
+#include "qqmlsa.h"
+#include "qqmlsa_p.h"
#include <QtCore/qqueue.h>
#include <QtCore/qsharedpointer.h>
@@ -26,13 +29,20 @@ QT_BEGIN_NAMESPACE
Multiple QQmlJSScope objects might be created for the same conceptual type, except when reused
due to extensive caching. Two QQmlJSScope objects are considered equal when they are backed
by the same implementation, that is, they have the same internalName.
- The qualifiedName of the QQmlJSScope for a type imported from multiple modules will contain the
- name of one of the modules that imported it, which is not unique and might change depending
- on the caching in .
*/
using namespace Qt::StringLiterals;
+QQmlJSScope::QQmlJSScope(const QString &internalName) : QQmlJSScope{}
+{
+ m_internalName = internalName;
+}
+
+QQmlJSScope::Ptr QQmlJSScope::create(const QString &internalName)
+{
+ return QSharedPointer<QQmlJSScope>(new QQmlJSScope(internalName));
+}
+
void QQmlJSScope::reparent(const QQmlJSScope::Ptr &parentScope, const QQmlJSScope::Ptr &childScope)
{
if (const QQmlJSScope::Ptr parent = childScope->m_parentScope.toStrongRef())
@@ -53,16 +63,25 @@ QQmlJSScope::Ptr QQmlJSScope::clone(const ConstPtr &origin)
return cloned;
}
+/*!
+\internal
+Return all the JavaScript identifiers defined in the current scope.
+*/
+QHash<QString, QQmlJSScope::JavaScriptIdentifier> QQmlJSScope::ownJSIdentifiers() const
+{
+ return m_jsIdentifiers;
+}
+
void QQmlJSScope::insertJSIdentifier(const QString &name, const JavaScriptIdentifier &identifier)
{
- Q_ASSERT(m_scopeType != QQmlJSScope::QMLScope);
+ Q_ASSERT(m_scopeType != QQmlSA::ScopeType::QMLScope);
if (identifier.kind == JavaScriptIdentifier::LexicalScoped
- || identifier.kind == JavaScriptIdentifier::Injected
- || m_scopeType == QQmlJSScope::JSFunctionScope) {
+ || identifier.kind == JavaScriptIdentifier::Injected
+ || m_scopeType == QQmlSA::ScopeType::JSFunctionScope) {
m_jsIdentifiers.insert(name, identifier);
} else {
auto targetScope = parentScope();
- while (targetScope->m_scopeType != QQmlJSScope::JSFunctionScope)
+ while (targetScope->m_scopeType != QQmlSA::ScopeType::JSFunctionScope)
targetScope = targetScope->parentScope();
targetScope->m_jsIdentifiers.insert(name, identifier);
}
@@ -71,17 +90,13 @@ void QQmlJSScope::insertJSIdentifier(const QString &name, const JavaScriptIdenti
void QQmlJSScope::insertPropertyIdentifier(const QQmlJSMetaProperty &property)
{
addOwnProperty(property);
- QQmlJSMetaMethod method(property.propertyName() + u"Changed"_s, u"void"_s);
- method.setMethodType(QQmlJSMetaMethod::Signal);
+ QQmlJSMetaMethod method(
+ QQmlSignalNames::propertyNameToChangedSignalName(property.propertyName()), u"void"_s);
+ method.setMethodType(QQmlJSMetaMethodType::Signal);
method.setIsImplicitQmlPropertyChangeSignal(true);
addOwnMethod(method);
}
-bool QQmlJSScope::isIdInCurrentScope(const QString &id) const
-{
- return isIdInCurrentQmlScopes(id) || isIdInCurrentJSScopes(id);
-}
-
bool QQmlJSScope::hasMethod(const QString &name) const
{
return QQmlJSUtils::searchBaseAndExtensionTypes(
@@ -133,7 +148,7 @@ QList<QQmlJSMetaMethod> QQmlJSScope::methods(const QString &name) const
return results;
}
-QList<QQmlJSMetaMethod> QQmlJSScope::methods(const QString &name, QQmlJSMetaMethod::Type type) const
+QList<QQmlJSMetaMethod> QQmlJSScope::methods(const QString &name, QQmlJSMetaMethodType type) const
{
QList<QQmlJSMetaMethod> results;
@@ -157,15 +172,19 @@ bool QQmlJSScope::hasEnumeration(const QString &name) const
this, [&](const QQmlJSScope *scope) { return scope->m_enumerations.contains(name); });
}
+bool QQmlJSScope::hasOwnEnumerationKey(const QString &name) const
+{
+ for (const auto &e : m_enumerations) {
+ if (e.keys().contains(name))
+ return true;
+ }
+ return false;
+}
+
bool QQmlJSScope::hasEnumerationKey(const QString &name) const
{
- return QQmlJSUtils::searchBaseAndExtensionTypes(this, [&](const QQmlJSScope *scope) {
- for (const auto &e : scope->m_enumerations) {
- if (e.keys().contains(name))
- return true;
- }
- return false;
- });
+ return QQmlJSUtils::searchBaseAndExtensionTypes(
+ this, [&](const QQmlJSScope *scope) { return scope->hasOwnEnumerationKey(name); });
}
QQmlJSMetaEnum QQmlJSScope::enumeration(const QString &name) const
@@ -199,6 +218,36 @@ QHash<QString, QQmlJSMetaEnum> QQmlJSScope::enumerations() const
return results;
}
+QString QQmlJSScope::augmentedInternalName() const
+{
+ using namespace Qt::StringLiterals;
+
+ switch (m_semantics) {
+ case AccessSemantics::Reference:
+ return m_internalName + " *"_L1;
+ case AccessSemantics::Value:
+ case AccessSemantics::Sequence:
+ break;
+ case AccessSemantics::None:
+ // If we got a namespace, it might still be a regular type, exposed as namespace.
+ // We may need to travel the inheritance chain all the way up to QObject to
+ // figure this out, since all other types may be exposed the same way.
+ for (QQmlJSScope::ConstPtr base = baseType(); base; base = base->baseType()) {
+ switch (base->accessSemantics()) {
+ case AccessSemantics::Reference:
+ return m_internalName + " *"_L1;
+ case AccessSemantics::Value:
+ case AccessSemantics::Sequence:
+ return m_internalName;
+ case AccessSemantics::None:
+ break;
+ }
+ }
+ break;
+ }
+ return m_internalName;
+}
+
QString QQmlJSScope::prettyName(QAnyStringView name)
{
const auto internal = "$internal$."_L1;
@@ -221,47 +270,6 @@ QString QQmlJSScope::prettyName(QAnyStringView name)
}
/*!
- Returns if assigning \a assignedType to \a property would require an
- implicit component wrapping.
- */
-bool QQmlJSScope::causesImplicitComponentWrapping(const QQmlJSMetaProperty &property,
- const QQmlJSScope::ConstPtr &assignedType)
-{
- // See QQmlComponentAndAliasResolver::findAndRegisterImplicitComponents()
- // for the logic in qqmltypecompiler
-
- // Note: unlike findAndRegisterImplicitComponents() we do not check whether
- // the property type is *derived* from QQmlComponent at some point because
- // this is actually meaningless (and in the case of QQmlComponent::create()
- // gets rejected in QQmlPropertyValidator): if the type is not a
- // QQmlComponent, we have a type mismatch because of assigning a Component
- // object to a non-Component property
- const bool propertyVerdict = property.type()->internalName() == u"QQmlComponent";
-
- const bool assignedTypeVerdict = [&assignedType]() {
- // Note: nonCompositeBaseType covers the case when assignedType itself
- // is non-composite
- auto cppBase = nonCompositeBaseType(assignedType);
- Q_ASSERT(cppBase); // any QML type has (or must have) a C++ base type
-
- // See isUsableComponent() in qqmltypecompiler.cpp: along with checking
- // whether a type has a QQmlComponent static meta object (which we
- // substitute here with checking the first non-composite base for being
- // a QQmlComponent), it also excludes QQmlAbstractDelegateComponent
- // subclasses from implicit wrapping
- if (cppBase->internalName() == u"QQmlComponent")
- return false;
- for (; cppBase; cppBase = cppBase->baseType()) {
- if (cppBase->internalName() == u"QQmlAbstractDelegateComponent")
- return false;
- }
- return true;
- }();
-
- return propertyVerdict && assignedTypeVerdict;
-}
-
-/*!
\internal
Returns true if the scope is the outermost element of a separate Component
Either because it has been implicitly wrapped, e.g. due to an assignment to
@@ -281,42 +289,12 @@ bool QQmlJSScope::isComponentRootElement() const {
return base->internalName() == u"QQmlComponent";
}
-bool QQmlJSScope::isIdInCurrentQmlScopes(const QString &id) const
-{
- if (m_scopeType == QQmlJSScope::QMLScope)
- return m_properties.contains(id) || m_methods.contains(id) || m_enumerations.contains(id);
-
- const auto qmlScope = findCurrentQMLScope(parentScope());
- return qmlScope->m_properties.contains(id)
- || qmlScope->m_methods.contains(id)
- || qmlScope->m_enumerations.contains(id);
-}
-
-bool QQmlJSScope::isIdInCurrentJSScopes(const QString &id) const
-{
- if (m_scopeType != QQmlJSScope::QMLScope && m_jsIdentifiers.contains(id))
- return true;
-
- for (auto jsScope = parentScope(); jsScope; jsScope = jsScope->parentScope()) {
- if (jsScope->m_scopeType != QQmlJSScope::QMLScope && jsScope->m_jsIdentifiers.contains(id))
- return true;
- }
-
- return false;
-}
-
-bool QQmlJSScope::isIdInjectedFromSignal(const QString &id) const
-{
- const auto found = findJSIdentifier(id);
- return found.has_value() && found->kind == JavaScriptIdentifier::Injected;
-}
-
std::optional<QQmlJSScope::JavaScriptIdentifier>
-QQmlJSScope::findJSIdentifier(const QString &id) const
+QQmlJSScope::jsIdentifier(const QString &id) const
{
for (const auto *scope = this; scope; scope = scope->parentScope().data()) {
- if (scope->m_scopeType == QQmlJSScope::JSFunctionScope
- || scope->m_scopeType == QQmlJSScope::JSLexicalScope) {
+ if (scope->m_scopeType == QQmlSA::ScopeType::JSFunctionScope
+ || scope->m_scopeType == QQmlSA::ScopeType::JSLexicalScope) {
auto it = scope->m_jsIdentifiers.find(id);
if (it != scope->m_jsIdentifiers.end())
return *it;
@@ -326,8 +304,17 @@ QQmlJSScope::findJSIdentifier(const QString &id) const
return std::optional<JavaScriptIdentifier>{};
}
+std::optional<QQmlJSScope::JavaScriptIdentifier> QQmlJSScope::ownJSIdentifier(const QString &id) const
+{
+ auto it = m_jsIdentifiers.find(id);
+ if (it != m_jsIdentifiers.end())
+ return *it;
+
+ return std::optional<JavaScriptIdentifier>{};
+}
+
static QQmlJSScope::ImportedScope<QQmlJSScope::ConstPtr>
-qFindInlineComponents(QStringView typeName, const QQmlJSScope::ContextualTypes &contextualTypes)
+qFindInlineComponents(QStringView typeName, const QQmlJS::ContextualTypes &contextualTypes)
{
const int separatorIndex = typeName.lastIndexOf(u'.');
// do not crash in typeName.sliced() when it starts or ends with an '.'.
@@ -363,8 +350,16 @@ qFindInlineComponents(QStringView typeName, const QQmlJSScope::ContextualTypes &
return {};
}
+/*! \internal
+ * Finds a type in contextualTypes with given name.
+ * If a type is found, then its name is inserted into usedTypes (when provided).
+ * If contextualTypes has mode INTERNAl, then namespace resolution for enums is
+ * done (eg for Qt::Alignment).
+ * If contextualTypes has mode QML, then inline component resolution is done
+ * ("qmlFileName.IC" is correctly resolved from qmlFileName).
+ */
QQmlJSScope::ImportedScope<QQmlJSScope::ConstPtr> QQmlJSScope::findType(
- const QString &name, const QQmlJSScope::ContextualTypes &contextualTypes,
+ const QString &name, const QQmlJS::ContextualTypes &contextualTypes,
QSet<QString> *usedTypes)
{
const auto useType = [&]() {
@@ -397,7 +392,7 @@ QQmlJSScope::ImportedScope<QQmlJSScope::ConstPtr> QQmlJSScope::findType(
};
switch (contextualTypes.context()) {
- case ContextualTypes::INTERNAL: {
+ case QQmlJS::ContextualTypes::INTERNAL: {
if (const auto listType = findListType(u"QList<"_s, u">"_s);
listType.scope && !listType.scope->isReferenceType()) {
return listType;
@@ -427,7 +422,7 @@ QQmlJSScope::ImportedScope<QQmlJSScope::ConstPtr> QQmlJSScope::findType(
break;
}
- case ContextualTypes::QML: {
+ case QQmlJS::ContextualTypes::QML: {
// look after inline components
const auto inlineComponent = qFindInlineComponents(name, contextualTypes);
if (inlineComponent.scope) {
@@ -445,7 +440,7 @@ QQmlJSScope::ImportedScope<QQmlJSScope::ConstPtr> QQmlJSScope::findType(
}
QTypeRevision QQmlJSScope::resolveType(
- const QQmlJSScope::Ptr &self, const QQmlJSScope::ContextualTypes &context,
+ const QQmlJSScope::Ptr &self, const QQmlJS::ContextualTypes &context,
QSet<QString> *usedTypes)
{
if (self->accessSemantics() == AccessSemantics::Sequence
@@ -469,6 +464,7 @@ QTypeRevision QQmlJSScope::resolveType(
if (self->accessSemantics() == AccessSemantics::Sequence) {
// All sequence types are implicitly extended by JS Array.
self->setExtensionTypeName(u"Array"_s);
+ self->setExtensionIsJavaScript(true);
self->m_extensionType = context.arrayType();
}
} else {
@@ -495,37 +491,47 @@ QTypeRevision QQmlJSScope::resolveType(
}
}
- for (auto it = self->m_methods.begin(), end = self->m_methods.end(); it != end; ++it) {
- const QString returnTypeName = it->returnTypeName();
- if (!it->returnType() && !returnTypeName.isEmpty()) {
- const auto returnType = findType(returnTypeName, context, usedTypes);
- it->setReturnType(returnType.scope);
- }
-
- auto parameters = it->parameters();
- for (int i = 0, length = parameters.size(); i < length; ++i) {
- auto &parameter = parameters[i];
- if (const QString typeName = parameter.typeName();
- !parameter.type() && !typeName.isEmpty()) {
- const auto type = findType(typeName, context, usedTypes);
- if (type.scope && type.scope->isReferenceType())
- parameter.setIsPointer(true);
- parameter.setType({ type.scope });
+ const auto resolveParameter = [&](QQmlJSMetaParameter &parameter) {
+ if (const QString typeName = parameter.typeName();
+ !parameter.type() && !typeName.isEmpty()) {
+ auto type = findType(typeName, context, usedTypes);
+ if (type.scope && parameter.isList()) {
+ type.scope = type.scope->listType();
+ parameter.setIsList(false);
+ parameter.setIsPointer(false);
+ parameter.setTypeName(type.scope ? type.scope->internalName() : QString());
+ } else if (type.scope && type.scope->isReferenceType()) {
+ parameter.setIsPointer(true);
}
+ parameter.setType({ type.scope });
}
+ };
+ for (auto it = self->m_methods.begin(), end = self->m_methods.end(); it != end; ++it) {
+ auto returnValue = it->returnValue();
+ resolveParameter(returnValue);
+ it->setReturnValue(returnValue);
+
+ auto parameters = it->parameters();
+ for (int i = 0, length = parameters.size(); i < length; ++i)
+ resolveParameter(parameters[i]);
it->setParameters(parameters);
}
+ for (auto it = self->m_jsIdentifiers.begin(); it != self->m_jsIdentifiers.end(); ++it) {
+ if (it->typeName)
+ it->scope = findType(it->typeName.value(), context, usedTypes).scope;
+ }
+
return baseType.revision;
}
void QQmlJSScope::updateChildScope(
const QQmlJSScope::Ptr &childScope, const QQmlJSScope::Ptr &self,
- const QQmlJSScope::ContextualTypes &contextualTypes, QSet<QString> *usedTypes)
+ const QQmlJS::ContextualTypes &contextualTypes, QSet<QString> *usedTypes)
{
switch (childScope->scopeType()) {
- case QQmlJSScope::GroupedPropertyScope:
+ case QQmlSA::ScopeType::GroupedPropertyScope:
QQmlJSUtils::searchBaseAndExtensionTypes(
self.data(), [&](const QQmlJSScope *type, QQmlJSScope::ExtensionKind mode) {
if (mode == QQmlJSScope::ExtensionNamespace)
@@ -541,7 +547,7 @@ void QQmlJSScope::updateChildScope(
return false;
});
break;
- case QQmlJSScope::AttachedPropertyScope:
+ case QQmlSA::ScopeType::AttachedPropertyScope:
if (const auto attachedBase = findType(
childScope->internalName(), contextualTypes, usedTypes).scope) {
childScope->m_baseType.scope = attachedBase->attachedType();
@@ -556,7 +562,7 @@ void QQmlJSScope::updateChildScope(
template<typename Resolver, typename ChildScopeUpdater>
static QTypeRevision resolveTypesInternal(
Resolver resolve, ChildScopeUpdater update, const QQmlJSScope::Ptr &self,
- const QQmlJSScope::ContextualTypes &contextualTypes, QSet<QString> *usedTypes)
+ const QQmlJS::ContextualTypes &contextualTypes, QSet<QString> *usedTypes)
{
const QTypeRevision revision = resolve(self, contextualTypes, usedTypes);
// NB: constness ensures no detach
@@ -570,13 +576,13 @@ static QTypeRevision resolveTypesInternal(
}
QTypeRevision QQmlJSScope::resolveTypes(
- const QQmlJSScope::Ptr &self, const QQmlJSScope::ContextualTypes &contextualTypes,
+ const QQmlJSScope::Ptr &self, const QQmlJS::ContextualTypes &contextualTypes,
QSet<QString> *usedTypes)
{
const auto resolveAll = [](const QQmlJSScope::Ptr &self,
- const QQmlJSScope::ContextualTypes &contextualTypes,
+ const QQmlJS::ContextualTypes &contextualTypes,
QSet<QString> *usedTypes) {
- resolveEnums(self, contextualTypes.intType());
+ resolveEnums(self, contextualTypes, usedTypes);
resolveList(self, contextualTypes.arrayType());
return resolveType(self, contextualTypes, usedTypes);
};
@@ -584,12 +590,35 @@ QTypeRevision QQmlJSScope::resolveTypes(
}
void QQmlJSScope::resolveNonEnumTypes(
- const QQmlJSScope::Ptr &self, const QQmlJSScope::ContextualTypes &contextualTypes,
+ const QQmlJSScope::Ptr &self, const QQmlJS::ContextualTypes &contextualTypes,
QSet<QString> *usedTypes)
{
resolveTypesInternal(resolveType, updateChildScope, self, contextualTypes, usedTypes);
}
+static QString flagStorage(const QString &underlyingType)
+{
+ // All numeric types are builtins. Therefore we can exhaustively check the internal names.
+
+ if (underlyingType == u"uint"
+ || underlyingType == u"quint8"
+ || underlyingType == u"ushort"
+ || underlyingType == u"ulonglong") {
+ return u"uint"_s;
+ }
+
+ if (underlyingType == u"int"
+ || underlyingType == u"qint8"
+ || underlyingType == u"short"
+ || underlyingType == u"longlong") {
+ return u"int"_s;
+ }
+
+ // Will fail to resolve and produce an error on usage.
+ // It's harmless if you never use the enum.
+ return QString();
+}
+
/*!
\internal
Resolves all enums of self.
@@ -601,20 +630,28 @@ void QQmlJSScope::resolveNonEnumTypes(
resolveEnums() will create a QQmlJSMetaEnum copy for the alias in case the 'self'-scope already
does not have an enum called like the alias.
*/
-void QQmlJSScope::resolveEnums(const QQmlJSScope::Ptr &self, const QQmlJSScope::ConstPtr &intType)
+void QQmlJSScope::resolveEnums(
+ const QQmlJSScope::Ptr &self, const QQmlJS::ContextualTypes &contextualTypes,
+ QSet<QString> *usedTypes)
{
// temporary hash to avoid messing up m_enumerations while iterators are active on it
QHash<QString, QQmlJSMetaEnum> toBeAppended;
- for (auto it = self->m_enumerations.begin(), end = self->m_enumerations.end(); it != end;
- ++it) {
+ for (auto it = self->m_enumerations.begin(), end = self->m_enumerations.end(); it != end; ++it) {
if (it->type())
continue;
- Q_ASSERT(intType); // We need an "int" type to resolve enums
QQmlJSScope::Ptr enumScope = QQmlJSScope::create();
reparent(self, enumScope);
- enumScope->m_scopeType = EnumScope;
- enumScope->setBaseTypeName(QStringLiteral("int"));
- enumScope->m_baseType.scope = intType;
+ enumScope->m_scopeType = QQmlSA::ScopeType::EnumScope;
+
+ QString typeName = it->typeName();
+ if (typeName.isEmpty())
+ typeName = QStringLiteral("int");
+ else if (it->isFlag())
+ typeName = flagStorage(typeName);
+ enumScope->setBaseTypeName(typeName);
+ const auto type = findType(typeName, contextualTypes, usedTypes);
+ enumScope->m_baseType = { type.scope, type.revision };
+
enumScope->m_semantics = AccessSemantics::Value;
enumScope->m_internalName = self->internalName() + QStringLiteral("::") + it->name();
if (QString alias = it->alias(); !alias.isEmpty()
@@ -656,9 +693,9 @@ void QQmlJSScope::resolveList(const QQmlJSScope::Ptr &self, const QQmlJSScope::C
const QQmlJSImportedScope element = {self, QTypeRevision()};
const QQmlJSImportedScope array = {arrayType, QTypeRevision()};
- QQmlJSScope::ContextualTypes contextualTypes(
- QQmlJSScope::ContextualTypes::INTERNAL, { { self->internalName(), element }, },
- QQmlJSScope::ConstPtr(), arrayType);
+ QQmlJS::ContextualTypes contextualTypes(
+ QQmlJS::ContextualTypes::INTERNAL, { { self->internalName(), element }, },
+ arrayType);
QQmlJSScope::resolveTypes(listType, contextualTypes);
Q_ASSERT(listType->valueType() == self);
@@ -667,7 +704,7 @@ void QQmlJSScope::resolveList(const QQmlJSScope::Ptr &self, const QQmlJSScope::C
void QQmlJSScope::resolveGeneralizedGroup(
const Ptr &self, const ConstPtr &baseType,
- const QQmlJSScope::ContextualTypes &contextualTypes, QSet<QString> *usedTypes)
+ const QQmlJS::ContextualTypes &contextualTypes, QSet<QString> *usedTypes)
{
Q_ASSERT(baseType);
// Generalized group properties are always composite,
@@ -682,7 +719,7 @@ void QQmlJSScope::resolveGeneralizedGroup(
QQmlJSScope::ConstPtr QQmlJSScope::findCurrentQMLScope(const QQmlJSScope::ConstPtr &scope)
{
auto qmlScope = scope;
- while (qmlScope && qmlScope->m_scopeType != QQmlJSScope::QMLScope)
+ while (qmlScope && qmlScope->m_scopeType != QQmlSA::ScopeType::QMLScope)
qmlScope = qmlScope->parentScope();
return qmlScope;
}
@@ -790,8 +827,21 @@ bool QQmlJSScope::isPropertyLocallyRequired(const QString &name) const
return m_requiredPropertyNames.contains(name);
}
-static_assert(QTypeInfo<QQmlJSScope::QmlIRCompatibilityBindingData>::isRelocatable,
- "We really want T to be relocatable as it improves QList<T> performance");
+void QQmlJSScope::addOwnPropertyBinding(const QQmlJSMetaPropertyBinding &binding, BindingTargetSpecifier specifier)
+{
+ Q_ASSERT(binding.sourceLocation().isValid());
+ m_propertyBindings.insert(binding.propertyName(), binding);
+
+ // NB: insert() prepends \a binding to the list of bindings, but we need
+ // append, so rotate
+ using iter = typename QMultiHash<QString, QQmlJSMetaPropertyBinding>::iterator;
+ QPair<iter, iter> r = m_propertyBindings.equal_range(binding.propertyName());
+ std::rotate(r.first, std::next(r.first), r.second);
+
+ // additionally store bindings in the QmlIR compatible order
+ addOwnPropertyBindingInQmlIROrder(binding, specifier);
+ Q_ASSERT(m_propertyBindings.size() == m_propertyBindingsArray.size());
+}
void QQmlJSScope::addOwnPropertyBindingInQmlIROrder(const QQmlJSMetaPropertyBinding &binding,
BindingTargetSpecifier specifier)
@@ -803,6 +853,9 @@ void QQmlJSScope::addOwnPropertyBindingInQmlIROrder(const QQmlJSMetaPropertyBind
// * bindings to default properties (which are not explicitly mentioned in
// binding expression) are inserted by source location's offset
+ static_assert(QTypeInfo<QQmlJSScope::QmlIRCompatibilityBindingData>::isRelocatable,
+ "We really want T to be relocatable as it improves QList<T> performance");
+
switch (specifier) {
case BindingTargetSpecifier::SimplePropertyTarget: {
m_propertyBindingsArray.emplaceFront(binding.propertyName(),
@@ -941,22 +994,6 @@ bool QQmlJSScope::isNameDeferred(const QString &name) const
return isDeferred;
}
-QString QQmlJSScope::qualifiedNameFrom(const QString &moduleName, const QString &typeName,
- const QTypeRevision &firstRevision,
- const QTypeRevision &lastRevision)
-{
- QString qualifiedName =
- u"%1/%2 %3.%4"_s.arg(moduleName, typeName)
- .arg(firstRevision.hasMajorVersion() ? firstRevision.majorVersion() : 0)
- .arg(firstRevision.hasMinorVersion() ? firstRevision.minorVersion() : 0);
- if (firstRevision != lastRevision) {
- qualifiedName += u"-%1.%2"_s
- .arg(lastRevision.hasMajorVersion() ? lastRevision.majorVersion() : 0)
- .arg(lastRevision.hasMinorVersion() ? lastRevision.minorVersion() : 0);
- }
- return qualifiedName;
-}
-
void QQmlJSScope::setBaseTypeName(const QString &baseTypeName)
{
m_flags.setFlag(HasBaseTypeError, false);
@@ -974,6 +1011,22 @@ void QQmlJSScope::setBaseTypeError(const QString &baseTypeError)
m_baseTypeNameOrError = baseTypeError;
}
+/*!
+\internal
+The name of the module is only saved in the QmlComponent. Iterate through the parent scopes until
+the QmlComponent or the root is reached to find out the module name of the component in which `this`
+resides.
+*/
+QString QQmlJSScope::moduleName() const
+{
+ for (const QQmlJSScope *it = this; it; it = it->parentScope().get()) {
+ const QString name = it->ownModuleName();
+ if (!name.isEmpty())
+ return name;
+ }
+ return {};
+}
+
QString QQmlJSScope::baseTypeError() const
{
return m_flags.testFlag(HasBaseTypeError) ? m_baseTypeNameOrError : QString();
@@ -1011,6 +1064,22 @@ QQmlJSScope::ConstPtr QQmlJSScope::attachedType() const
return ptr;
}
+QQmlJSScope::AnnotatedScope QQmlJSScope::extensionType() const
+{
+ if (!m_extensionType)
+ return { m_extensionType, NotExtension };
+ if (m_flags & ExtensionIsJavaScript)
+ return { m_extensionType, ExtensionJavaScript };
+ if (m_flags & ExtensionIsNamespace)
+ return { m_extensionType, ExtensionNamespace };
+ return { m_extensionType, ExtensionType };
+}
+
+void QQmlJSScope::addOwnRuntimeFunctionIndex(QQmlJSMetaMethod::AbsoluteFunctionIndex index)
+{
+ m_runtimeFunctionIndices.emplaceBack(index);
+}
+
bool QQmlJSScope::isResolved() const
{
const bool nameIsEmpty = (m_scopeType == ScopeType::AttachedPropertyScope
@@ -1060,21 +1129,6 @@ bool QQmlJSScope::isFullyResolved() const
return baseResolved;
}
-QQmlJSScope::Import::Import(QString prefix, QString name, QTypeRevision version, bool isFile,
- bool isDependency) :
- m_prefix(std::move(prefix)),
- m_name(std::move(name)),
- m_version(version),
- m_isFile(isFile),
- m_isDependency(isDependency)
-{
-}
-
-bool QQmlJSScope::Import::isValid() const
-{
- return !m_name.isEmpty();
-}
-
QQmlJSScope::Export::Export(
QString package, QString type, QTypeRevision version, QTypeRevision revision)
: m_package(std::move(package))
@@ -1089,15 +1143,29 @@ bool QQmlJSScope::Export::isValid() const
return m_version.isValid() || !m_package.isEmpty() || !m_type.isEmpty();
}
+QDeferredFactory<QQmlJSScope>::QDeferredFactory(QQmlJSImporter *importer, const QString &filePath,
+ const TypeReader &typeReader)
+ : m_filePath(filePath),
+ m_importer(importer),
+ m_typeReader(typeReader ? typeReader
+ : [](QQmlJSImporter *importer, const QString &filePath,
+ const QSharedPointer<QQmlJSScope> &scopeToPopulate) {
+ QQmlJSTypeReader defaultTypeReader(importer, filePath);
+ defaultTypeReader(scopeToPopulate);
+ return defaultTypeReader.errors();
+ })
+{
+}
+
void QDeferredFactory<QQmlJSScope>::populate(const QSharedPointer<QQmlJSScope> &scope) const
{
- scope->setQualifiedName(m_qualifiedName);
- scope->setModuleName(m_moduleName);
- QQmlJSTypeReader typeReader(m_importer, m_filePath);
- typeReader(scope);
- m_importer->m_globalWarnings.append(typeReader.errors());
+ scope->setOwnModuleName(m_moduleName);
+
+ QList<QQmlJS::DiagnosticMessage> errors = m_typeReader(m_importer, m_filePath, scope);
+ m_importer->m_globalWarnings.append(errors);
+
scope->setInternalName(internalName());
- QQmlJSScope::resolveEnums(scope, m_importer->builtinInternalNames().intType());
+ QQmlJSScope::resolveEnums(scope, m_importer->builtinInternalNames());
QQmlJSScope::resolveList(scope, m_importer->builtinInternalNames().arrayType());
if (m_isSingleton && !scope->isSingleton()) {
@@ -1117,6 +1185,15 @@ void QDeferredFactory<QQmlJSScope>::populate(const QSharedPointer<QQmlJSScope> &
}
}
+/*!
+ \internal
+ Checks whether \a derived type can be assigned to this type. Returns \c
+ true if the type hierarchy of \a derived contains a type equal to this.
+
+ \note Assigning \a derived to "QVariant" or "QJSValue" is always possible and
+ the function returns \c true in this case. In addition any "QObject" based \a derived type
+ can be assigned to a this type if that type is derived from "QQmlComponent".
+ */
bool QQmlJSScope::canAssign(const QQmlJSScope::ConstPtr &derived) const
{
if (!derived)
@@ -1156,6 +1233,10 @@ bool QQmlJSScope::canAssign(const QQmlJSScope::ConstPtr &derived) const
return isListProperty() && valueType()->canAssign(derived);
}
+/*!
+ \internal
+ Checks whether this type or its parents have a custom parser.
+*/
bool QQmlJSScope::isInCustomParserParent() const
{
for (const auto *scope = this; scope; scope = scope->parentScope().get()) {
@@ -1191,13 +1272,22 @@ QQmlJSScope::InlineComponentOrDocumentRootName QQmlJSScope::enclosingInlineCompo
return RootDocumentNameType();
}
+QVector<QQmlJSScope::ConstPtr> QQmlJSScope::childScopes() const
+{
+ QVector<QQmlJSScope::ConstPtr> result;
+ result.reserve(m_childScopes.size());
+ for (const auto &child : m_childScopes)
+ result.append(child);
+ return result;
+}
+
/*!
\internal
Returns true if the current type is creatable by checking all the required base classes.
"Uncreatability" is only inherited from base types for composite types (in qml) and not for non-composite types (c++).
- For the exact definition:
- A type is uncreatable if and only if one of its composite base type or its first non-composite base type matches
+For the exact definition:
+A type is uncreatable if and only if one of its composite base type or its first non-composite base type matches
following criteria:
\list
\li the base type is a singleton, or
@@ -1209,7 +1299,8 @@ QQmlJSScope::InlineComponentOrDocumentRootName QQmlJSScope::enclosingInlineCompo
bool QQmlJSScope::isCreatable() const
{
auto isCreatableNonRecursive = [](const QQmlJSScope *scope) {
- return scope->hasCreatableFlag() && !scope->isSingleton() && scope->scopeType() == QMLScope;
+ return scope->hasCreatableFlag() && !scope->isSingleton()
+ && scope->scopeType() == QQmlSA::ScopeType::QMLScope;
};
for (const QQmlJSScope* scope = this; scope; scope = scope->baseType().get()) {
@@ -1225,4 +1316,68 @@ bool QQmlJSScope::isCreatable() const
// no uncreatable bases found
return false;
}
+
+bool QQmlJSScope::isStructured() const
+{
+ for (const QQmlJSScope *scope = this; scope; scope = scope->baseType().get()) {
+ if (!scope->isComposite())
+ return scope->hasStructuredFlag();
+ }
+ return false;
+}
+
+QQmlSA::Element QQmlJSScope::createQQmlSAElement(const ConstPtr &ptr)
+{
+ QQmlSA::Element element;
+ *reinterpret_cast<QQmlJSScope::ConstPtr *>(element.m_data) = ptr;
+ return element;
+}
+
+QQmlSA::Element QQmlJSScope::createQQmlSAElement(ConstPtr &&ptr)
+{
+ QQmlSA::Element element;
+ *reinterpret_cast<QQmlJSScope::ConstPtr *>(element.m_data) = std::move(ptr);
+ return element;
+}
+
+const QQmlJSScope::ConstPtr &QQmlJSScope::scope(const QQmlSA::Element &element)
+{
+ return *reinterpret_cast<const QQmlJSScope::ConstPtr *>(element.m_data);
+}
+
+QTypeRevision
+QQmlJSScope::nonCompositeBaseRevision(const ImportedScope<QQmlJSScope::ConstPtr> &scope)
+{
+ for (auto base = scope; base.scope;
+ base = { base.scope->m_baseType.scope, base.scope->m_baseType.revision }) {
+ if (!base.scope->isComposite())
+ return base.revision;
+ }
+ return {};
+}
+
+/*!
+ \internal
+ Checks whether \a otherScope is the same type as this.
+
+ In addition to checking whether the scopes are identical, we also cover duplicate scopes with
+ the same internal name.
+ */
+bool QQmlJSScope::isSameType(const ConstPtr &otherScope) const
+{
+ return this == otherScope.get()
+ || (!this->internalName().isEmpty()
+ && this->internalName() == otherScope->internalName());
+}
+
+bool QQmlJSScope::inherits(const ConstPtr &base) const
+{
+ for (const QQmlJSScope *scope = this; scope; scope = scope->baseType().get()) {
+ if (scope->isSameType(base))
+ return true;
+ }
+ return false;
+}
+
+
QT_END_NAMESPACE
diff --git a/src/qmlcompiler/qqmljsscope_p.h b/src/qmlcompiler/qqmljsscope_p.h
index aab9b472b8..b0bc6818a3 100644
--- a/src/qmlcompiler/qqmljsscope_p.h
+++ b/src/qmlcompiler/qqmljsscope_p.h
@@ -14,11 +14,13 @@
//
// We mean it.
-#include <private/qtqmlcompilerexports_p.h>
+#include <qtqmlcompilerexports.h>
#include "qqmljsmetatypes_p.h"
#include "qdeferredpointer_p.h"
#include "qqmljsannotation_p.h"
+#include "qqmlsaconstants.h"
+#include "qqmlsa_p.h"
#include <QtQml/private/qqmljssourcelocation_p.h>
@@ -27,6 +29,7 @@
#include <QtCore/qset.h>
#include <QtCore/qstring.h>
#include <QtCore/qversionnumber.h>
+#include "qqmlsaconstants.h"
#include <optional>
@@ -34,9 +37,102 @@ QT_BEGIN_NAMESPACE
class QQmlJSImporter;
-class Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSScope
+namespace QQmlJS {
+
+class ConstPtrWrapperIterator
{
public:
+ using Ptr = QDeferredSharedPointer<QQmlJSScope>;
+ using ConstPtr = QDeferredSharedPointer<const QQmlJSScope>;
+ using iterator_category = std::forward_iterator_tag;
+ using difference_type = std::ptrdiff_t;
+ using value_type = ConstPtr;
+ using pointer = value_type *;
+ using reference = value_type &;
+
+ ConstPtrWrapperIterator(QList<Ptr>::const_iterator iterator) : m_iterator(iterator) { }
+
+ friend bool operator==(const ConstPtrWrapperIterator &a, const ConstPtrWrapperIterator &b)
+ {
+ return a.m_iterator == b.m_iterator;
+ }
+ friend bool operator!=(const ConstPtrWrapperIterator &a, const ConstPtrWrapperIterator &b)
+ {
+ return a.m_iterator != b.m_iterator;
+ }
+
+ reference operator*()
+ {
+ if (!m_pointer)
+ m_pointer = *m_iterator;
+ return m_pointer;
+ }
+ pointer operator->()
+ {
+ if (!m_pointer)
+ m_pointer = *m_iterator;
+ return &m_pointer;
+ }
+
+ ConstPtrWrapperIterator &operator++()
+ {
+ m_iterator++;
+ m_pointer = {};
+ return *this;
+ }
+ ConstPtrWrapperIterator operator++(int)
+ {
+ auto before = *this;
+ ++(*this);
+ return before;
+ }
+
+private:
+ QList<Ptr>::const_iterator m_iterator;
+ ConstPtr m_pointer;
+};
+
+class Export {
+public:
+ Export() = default;
+ Export(QString package, QString type, QTypeRevision version, QTypeRevision revision);
+
+ bool isValid() const;
+
+ QString package() const { return m_package; }
+ QString type() const { return m_type; }
+ QTypeRevision version() const { return m_version; }
+ QTypeRevision revision() const { return m_revision; }
+
+private:
+ QString m_package;
+ QString m_type;
+ QTypeRevision m_version;
+ QTypeRevision m_revision;
+};
+
+template<typename Pointer>
+struct ExportedScope {
+ Pointer scope;
+ QList<Export> exports;
+};
+
+template<typename Pointer>
+struct ImportedScope {
+ Pointer scope;
+ QTypeRevision revision;
+};
+
+struct ContextualTypes;
+
+} // namespace QQmlJS
+
+class Q_QMLCOMPILER_EXPORT QQmlJSScope
+{
+ friend QQmlSA::Element;
+
+public:
+ explicit QQmlJSScope(const QString &internalName);
QQmlJSScope(QQmlJSScope &&) = default;
QQmlJSScope &operator=(QQmlJSScope &&) = default;
@@ -45,6 +141,9 @@ public:
using ConstPtr = QDeferredSharedPointer<const QQmlJSScope>;
using WeakConstPtr = QDeferredWeakPointer<const QQmlJSScope>;
+ using AccessSemantics = QQmlSA::AccessSemantics;
+ using ScopeType = QQmlSA::ScopeType;
+
using InlineComponentNameType = QString;
using RootDocumentNameType = std::monostate; // an empty type that has std::hash
/*!
@@ -53,23 +152,6 @@ public:
using InlineComponentOrDocumentRootName =
std::variant<InlineComponentNameType, RootDocumentNameType>;
- enum ScopeType
- {
- JSFunctionScope,
- JSLexicalScope,
- QMLScope,
- GroupedPropertyScope,
- AttachedPropertyScope,
- EnumScope
- };
-
- enum class AccessSemantics {
- Reference,
- Value,
- None,
- Sequence
- };
-
enum Flag {
Creatable = 0x1,
Composite = 0x2,
@@ -80,197 +162,19 @@ public:
InlineComponent = 0x40,
WrappedInImplicitComponent = 0x80,
HasBaseTypeError = 0x100,
- HasExtensionNamespace = 0x200,
+ ExtensionIsNamespace = 0x200,
IsListProperty = 0x400,
+ Structured = 0x800,
+ ExtensionIsJavaScript = 0x1000,
};
Q_DECLARE_FLAGS(Flags, Flag)
Q_FLAGS(Flags);
- class ConstPtrWrapperIterator
- {
- public:
- using iterator_category = std::forward_iterator_tag;
- using difference_type = std::ptrdiff_t;
- using value_type = ConstPtr;
- using pointer = value_type *;
- using reference = value_type &;
-
- ConstPtrWrapperIterator(QList<QQmlJSScope::Ptr>::const_iterator iterator)
- : m_iterator(iterator)
- {
- }
-
- friend bool operator==(const ConstPtrWrapperIterator &a, const ConstPtrWrapperIterator &b)
- {
- return a.m_iterator == b.m_iterator;
- }
- friend bool operator!=(const ConstPtrWrapperIterator &a, const ConstPtrWrapperIterator &b)
- {
- return a.m_iterator != b.m_iterator;
- }
-
- reference operator*()
- {
- if (!m_pointer)
- m_pointer = *m_iterator;
- return m_pointer;
- }
- pointer operator->()
- {
- if (!m_pointer)
- m_pointer = *m_iterator;
- return &m_pointer;
- }
-
- ConstPtrWrapperIterator &operator++()
- {
- m_iterator++;
- m_pointer = {};
- return *this;
- }
- ConstPtrWrapperIterator operator++(int)
- {
- auto before = *this;
- ++(*this);
- return before;
- }
-
- private:
- QList<QQmlJSScope::Ptr>::const_iterator m_iterator;
- QQmlJSScope::ConstPtr m_pointer;
- };
-
- class Import
- {
- public:
- Import() = default;
- Import(QString prefix, QString name, QTypeRevision version, bool isFile, bool isDependency);
-
- bool isValid() const;
-
- QString prefix() const { return m_prefix; }
- QString name() const { return m_name; }
- QTypeRevision version() const { return m_version; }
- bool isFile() const { return m_isFile; }
- bool isDependency() const { return m_isDependency; }
-
- private:
- QString m_prefix;
- QString m_name;
- QTypeRevision m_version;
- bool m_isFile = false;
- bool m_isDependency = false;
-
- friend inline size_t qHash(const Import &key, size_t seed = 0) noexcept
- {
- return qHashMulti(seed, key.m_prefix, key.m_name, key.m_version,
- key.m_isFile, key.m_isDependency);
- }
-
- friend inline bool operator==(const Import &a, const Import &b)
- {
- return a.m_prefix == b.m_prefix && a.m_name == b.m_name && a.m_version == b.m_version
- && a.m_isFile == b.m_isFile && a.m_isDependency == b.m_isDependency;
- }
- };
-
- class Export {
- public:
- Export() = default;
- Export(QString package, QString type, QTypeRevision version, QTypeRevision revision);
-
- bool isValid() const;
-
- QString package() const { return m_package; }
- QString type() const { return m_type; }
- QTypeRevision version() const { return m_version; }
- QTypeRevision revision() const { return m_revision; }
-
- private:
- QString m_package;
- QString m_type;
- QTypeRevision m_version;
- QTypeRevision m_revision;
- };
-
- template<typename Pointer>
- struct ExportedScope {
- Pointer scope;
- QList<QQmlJSScope::Export> exports;
- };
-
- template<typename Pointer>
- struct ImportedScope {
- Pointer scope;
- QTypeRevision revision;
- };
-
- /*! \internal
- * Maps type names to types and the compile context of the types. The context can be
- * INTERNAl (for c++ and synthetic jsrootgen types) or QML (for qml types).
- */
- struct ContextualTypes
- {
- enum CompileContext { INTERNAL, QML };
-
- ContextualTypes(
- CompileContext context,
- const QHash<QString, ImportedScope<ConstPtr>> types,
- const QQmlJSScope::ConstPtr &intType,
- const QQmlJSScope::ConstPtr &arrayType)
- : m_types(types)
- , m_context(context)
- , m_intType(intType)
- , m_arrayType(arrayType)
- {}
-
- CompileContext context() const { return m_context; }
- ConstPtr intType() const { return m_intType; }
- ConstPtr arrayType() const { return m_arrayType; }
-
- bool hasType(const QString &name) const { return m_types.contains(name); }
- ImportedScope<ConstPtr> type(const QString &name) const { return m_types[name]; }
- void setType(const QString &name, const ImportedScope<ConstPtr> &type)
- {
- m_types.insert(name, type);
- }
- void clearType(const QString &name)
- {
- m_types[name].scope = QQmlJSScope::ConstPtr();
- }
-
- bool isNullType(const QString &name) const
- {
- const auto it = m_types.constFind(name);
- return it != m_types.constEnd() && it->scope.isNull();
- }
-
- void addTypes(ContextualTypes &&types)
- {
- Q_ASSERT(types.m_context == m_context);
- m_types.insert(std::move(types.m_types));
- }
-
- void addTypes(const ContextualTypes &types)
- {
- Q_ASSERT(types.m_context == m_context);
- m_types.insert(types.m_types);
- }
-
- const QHash<QString, ImportedScope<ConstPtr>> &types() const { return m_types; }
-
- void clearTypes() { m_types.clear(); }
-
- private:
- QHash<QString, ImportedScope<ConstPtr>> m_types;
- CompileContext m_context;
-
- // For resolving enums
- QQmlJSScope::ConstPtr m_intType;
-
- // For resolving sequence types
- QQmlJSScope::ConstPtr m_arrayType;
- };
+ using Export = QQmlJS::Export;
+ template <typename Pointer>
+ using ImportedScope = QQmlJS::ImportedScope<Pointer>;
+ template <typename Pointer>
+ using ExportedScope = QQmlJS::ExportedScope<Pointer>;
struct JavaScriptIdentifier
{
@@ -283,7 +187,9 @@ public:
Kind kind = FunctionScoped;
QQmlJS::SourceLocation location;
- bool isConst;
+ std::optional<QString> typeName;
+ bool isConst = false;
+ QQmlJSScope::WeakConstPtr scope = {};
};
enum BindingTargetSpecifier {
@@ -292,38 +198,32 @@ public:
UnnamedPropertyTarget // default property bindings, where property name is unspecified
};
+ template <typename Key, typename Value>
+ using QMultiHashRange = QPair<typename QMultiHash<Key, Value>::iterator,
+ typename QMultiHash<Key, Value>::iterator>;
+
static QQmlJSScope::Ptr create() { return QSharedPointer<QQmlJSScope>(new QQmlJSScope); }
+ static QQmlJSScope::Ptr create(const QString &internalName);
static QQmlJSScope::Ptr clone(const QQmlJSScope::ConstPtr &origin);
- static QQmlJSScope::ConstPtr findCurrentQMLScope(const QQmlJSScope::ConstPtr &scope);
- QQmlJSScope::Ptr parentScope()
- {
- return m_parentScope.toStrongRef();
- }
-
- QQmlJSScope::ConstPtr parentScope() const
- {
-QT_WARNING_PUSH
-#if defined(Q_CC_GNU_ONLY) && Q_CC_GNU < 1400 && Q_CC_GNU >= 1200
- QT_WARNING_DISABLE_GCC("-Wuse-after-free")
-#endif
- return QQmlJSScope::WeakConstPtr(m_parentScope).toStrongRef();
-QT_WARNING_POP
- }
+ static QQmlJSScope::ConstPtr findCurrentQMLScope(const QQmlJSScope::ConstPtr &scope);
+ QQmlJSScope::Ptr parentScope();
+ QQmlJSScope::ConstPtr parentScope() const;
static void reparent(const QQmlJSScope::Ptr &parentScope, const QQmlJSScope::Ptr &childScope);
void insertJSIdentifier(const QString &name, const JavaScriptIdentifier &identifier);
-
- // inserts property as qml identifier as well as the corresponding
+ QHash<QString, JavaScriptIdentifier> ownJSIdentifiers() const;
void insertPropertyIdentifier(const QQmlJSMetaProperty &prop);
- bool isIdInCurrentScope(const QString &id) const;
-
ScopeType scopeType() const { return m_scopeType; }
void setScopeType(ScopeType type) { m_scopeType = type; }
void addOwnMethod(const QQmlJSMetaMethod &method) { m_methods.insert(method.methodName(), method); }
+ QMultiHashRange<QString, QQmlJSMetaMethod> mutableOwnMethodsRange(const QString &name)
+ {
+ return m_methods.equal_range(name);
+ }
QMultiHash<QString, QQmlJSMetaMethod> ownMethods() const { return m_methods; }
QList<QQmlJSMetaMethod> ownMethods(const QString &name) const { return m_methods.values(name); }
bool hasOwnMethod(const QString &name) const { return m_methods.contains(name); }
@@ -331,7 +231,7 @@ QT_WARNING_POP
bool hasMethod(const QString &name) const;
QHash<QString, QQmlJSMetaMethod> methods() const;
QList<QQmlJSMetaMethod> methods(const QString &name) const;
- QList<QQmlJSMetaMethod> methods(const QString &name, QQmlJSMetaMethod::Type type) const;
+ QList<QQmlJSMetaMethod> methods(const QString &name, QQmlJSMetaMethodType type) const;
void addOwnEnumeration(const QQmlJSMetaEnum &enumeration) { m_enumerations.insert(enumeration.name(), enumeration); }
QHash<QString, QQmlJSMetaEnum> ownEnumerations() const { return m_enumerations; }
@@ -340,6 +240,7 @@ QT_WARNING_POP
bool hasEnumeration(const QString &name) const;
bool hasEnumerationKey(const QString &name) const;
+ bool hasOwnEnumerationKey(const QString &name) const;
QQmlJSMetaEnum enumeration(const QString &name) const;
QHash<QString, QQmlJSMetaEnum> enumerations() const;
@@ -353,43 +254,16 @@ QT_WARNING_POP
// QML file. isComposite tells us if this is a C++ or a QML name.
QString internalName() const { return m_internalName; }
void setInternalName(const QString &internalName) { m_internalName = internalName; }
- QString augmentedInternalName() const
- {
- using namespace Qt::StringLiterals;
-
- switch (m_semantics) {
- case AccessSemantics::Reference:
- return m_internalName + " *"_L1;
- case AccessSemantics::Value:
- case AccessSemantics::Sequence:
- break;
- case AccessSemantics::None:
- // If we got a namespace, it might still be a regular type, exposed as namespace.
- // We may need to travel the inheritance chain all the way up to QObject to
- // figure this out, since all other types may be exposed the same way.
- for (QQmlJSScope::ConstPtr base = baseType(); base; base = base->baseType()) {
- switch (base->accessSemantics()) {
- case AccessSemantics::Reference:
- return m_internalName + " *"_L1;
- case AccessSemantics::Value:
- case AccessSemantics::Sequence:
- return m_internalName;
- case AccessSemantics::None:
- break;
- }
- }
- break;
- }
- return m_internalName;
- }
+ QString augmentedInternalName() const;
// This returns a more user readable version of internalName / baseTypeName
static QString prettyName(QAnyStringView name);
- static bool causesImplicitComponentWrapping(const QQmlJSMetaProperty &property,
- const QQmlJSScope::ConstPtr &assignedType);
bool isComponentRootElement() const;
+ void setAliases(const QStringList &aliases) { m_aliases = aliases; }
+ QStringList aliases() const { return m_aliases; }
+
void setInterfaceNames(const QStringList& interfaces) { m_interfaceNames = interfaces; }
QStringList interfaceNames() const { return m_interfaceNames; }
@@ -411,13 +285,9 @@ QT_WARNING_POP
QQmlJSScope::ConstPtr baseType() const { return m_baseType.scope; }
QTypeRevision baseTypeRevision() const { return m_baseType.revision; }
- QString qualifiedName() const { return m_qualifiedName; }
- void setQualifiedName(const QString &qualifiedName) { m_qualifiedName = qualifiedName; };
- static QString qualifiedNameFrom(const QString &moduleName, const QString &typeName,
- const QTypeRevision &firstRevision,
- const QTypeRevision &lastRevision);
- QString moduleName() const { return m_moduleName; }
- void setModuleName(const QString &moduleName) { m_moduleName = moduleName; }
+ QString moduleName() const;
+ QString ownModuleName() const { return m_moduleName; }
+ void setOwnModuleName(const QString &moduleName) { m_moduleName = moduleName; }
void clearBaseType() { m_baseType = {}; }
void setBaseTypeError(const QString &baseTypeError);
@@ -438,36 +308,13 @@ QT_WARNING_POP
void addOwnPropertyBinding(
const QQmlJSMetaPropertyBinding &binding,
- BindingTargetSpecifier specifier = BindingTargetSpecifier::SimplePropertyTarget)
- {
- Q_ASSERT(binding.sourceLocation().isValid());
- m_propertyBindings.insert(binding.propertyName(), binding);
-
- // NB: insert() prepends \a binding to the list of bindings, but we need
- // append, so rotate
- using iter = typename QMultiHash<QString, QQmlJSMetaPropertyBinding>::iterator;
- QPair<iter, iter> r = m_propertyBindings.equal_range(binding.propertyName());
- std::rotate(r.first, std::next(r.first), r.second);
-
- // additionally store bindings in the QmlIR compatible order
- addOwnPropertyBindingInQmlIROrder(binding, specifier);
- Q_ASSERT(m_propertyBindings.size() == m_propertyBindingsArray.size());
- }
- QMultiHash<QString, QQmlJSMetaPropertyBinding> ownPropertyBindings() const
- {
- return m_propertyBindings;
- }
+ BindingTargetSpecifier specifier = BindingTargetSpecifier::SimplePropertyTarget);
+ QMultiHash<QString, QQmlJSMetaPropertyBinding> ownPropertyBindings() const;
QPair<QMultiHash<QString, QQmlJSMetaPropertyBinding>::const_iterator,
QMultiHash<QString, QQmlJSMetaPropertyBinding>::const_iterator>
- ownPropertyBindings(const QString &name) const
- {
- return m_propertyBindings.equal_range(name);
- }
+ ownPropertyBindings(const QString &name) const;
QList<QQmlJSMetaPropertyBinding> ownPropertyBindingsInQmlIROrder() const;
- bool hasOwnPropertyBindings(const QString &name) const
- {
- return m_propertyBindings.contains(name);
- }
+ bool hasOwnPropertyBindings(const QString &name) const;
bool hasPropertyBindings(const QString &name) const;
QList<QQmlJSMetaPropertyBinding> propertyBindings(const QString &name) const;
@@ -498,6 +345,7 @@ QT_WARNING_POP
enum ExtensionKind {
NotExtension,
ExtensionType,
+ ExtensionJavaScript,
ExtensionNamespace,
};
struct AnnotatedScope
@@ -505,13 +353,7 @@ QT_WARNING_POP
QQmlJSScope::ConstPtr scope;
ExtensionKind extensionSpecifier = NotExtension;
};
- AnnotatedScope extensionType() const
- {
- if (!m_extensionType)
- return { m_extensionType, NotExtension };
- return { m_extensionType,
- (m_flags & HasExtensionNamespace) ? ExtensionNamespace : ExtensionType };
- }
+ AnnotatedScope extensionType() const;
QString valueTypeName() const { return m_valueTypeName; }
void setValueTypeName(const QString &name) { m_valueTypeName = name; }
@@ -519,22 +361,11 @@ QT_WARNING_POP
QQmlJSScope::ConstPtr listType() const { return m_listType; }
QQmlJSScope::Ptr listType() { return m_listType; }
- void addOwnRuntimeFunctionIndex(QQmlJSMetaMethod::AbsoluteFunctionIndex index)
- {
- m_runtimeFunctionIndices.emplaceBack(index);
- }
+ void addOwnRuntimeFunctionIndex(QQmlJSMetaMethod::AbsoluteFunctionIndex index);
QQmlJSMetaMethod::AbsoluteFunctionIndex
- ownRuntimeFunctionIndex(QQmlJSMetaMethod::RelativeFunctionIndex index) const
- {
- const int i = static_cast<int>(index);
- Q_ASSERT(i >= 0);
- Q_ASSERT(i < int(m_runtimeFunctionIndices.size()));
- return m_runtimeFunctionIndices[i];
- }
+ ownRuntimeFunctionIndex(QQmlJSMetaMethod::RelativeFunctionIndex index) const;
+
- bool isSingleton() const { return m_flags & Singleton; }
- bool isCreatable() const;
- bool hasCreatableFlag() const { return m_flags & Creatable; }
/*!
* \internal
*
@@ -546,148 +377,94 @@ QT_WARNING_POP
bool isArrayScope() const { return m_flags & Array; }
bool isInlineComponent() const { return m_flags & InlineComponent; }
bool isWrappedInImplicitComponent() const { return m_flags & WrappedInImplicitComponent; }
- bool extensionIsNamespace() const { return m_flags & HasExtensionNamespace; }
+ bool extensionIsJavaScript() const { return m_flags & ExtensionIsJavaScript; }
+ bool extensionIsNamespace() const { return m_flags & ExtensionIsNamespace; }
+ bool isListProperty() const { return m_flags.testFlag(IsListProperty); }
+ void setIsListProperty(bool v) { m_flags.setFlag(IsListProperty, v); }
+ bool isSingleton() const { return m_flags & Singleton; }
+ bool isCreatable() const;
+ bool isStructured() const;
+ bool isReferenceType() const { return m_semantics == QQmlJSScope::AccessSemantics::Reference; }
+ bool isValueType() const { return m_semantics == QQmlJSScope::AccessSemantics::Value; }
+
void setIsSingleton(bool v) { m_flags.setFlag(Singleton, v); }
void setCreatableFlag(bool v) { m_flags.setFlag(Creatable, v); }
+ void setStructuredFlag(bool v) { m_flags.setFlag(Structured, v); }
void setIsComposite(bool v) { m_flags.setFlag(Composite, v); }
void setIsScript(bool v) { m_flags.setFlag(Script, v); }
- void setHasCustomParser(bool v)
- {
- m_flags.setFlag(CustomParser, v);;
- }
+ void setHasCustomParser(bool v);
void setIsArrayScope(bool v) { m_flags.setFlag(Array, v); }
void setIsInlineComponent(bool v) { m_flags.setFlag(InlineComponent, v); }
void setIsWrappedInImplicitComponent(bool v) { m_flags.setFlag(WrappedInImplicitComponent, v); }
- void setExtensionIsNamespace(bool v) { m_flags.setFlag(HasExtensionNamespace, v); }
+ void setExtensionIsJavaScript(bool v) { m_flags.setFlag(ExtensionIsJavaScript, v); }
+ void setExtensionIsNamespace(bool v) { m_flags.setFlag(ExtensionIsNamespace, v); }
- bool isListProperty() const { return m_flags.testFlag(IsListProperty); }
- void setIsListProperty(bool v) { m_flags.setFlag(IsListProperty, v); }
void setAccessSemantics(AccessSemantics semantics) { m_semantics = semantics; }
AccessSemantics accessSemantics() const { return m_semantics; }
- bool isReferenceType() const { return m_semantics == QQmlJSScope::AccessSemantics::Reference; }
- bool isValueType() const { return m_semantics == QQmlJSScope::AccessSemantics::Value; }
-
- bool isIdInCurrentQmlScopes(const QString &id) const;
- bool isIdInCurrentJSScopes(const QString &id) const;
- bool isIdInjectedFromSignal(const QString &id) const;
- std::optional<JavaScriptIdentifier> findJSIdentifier(const QString &id) const;
+ std::optional<JavaScriptIdentifier> jsIdentifier(const QString &id) const;
+ std::optional<JavaScriptIdentifier> ownJSIdentifier(const QString &id) const;
- ConstPtrWrapperIterator childScopesBegin() const { return m_childScopes.constBegin(); }
- ConstPtrWrapperIterator childScopesEnd() const { return m_childScopes.constEnd(); }
+ QQmlJS::ConstPtrWrapperIterator childScopesBegin() const { return m_childScopes.constBegin(); }
+ QQmlJS::ConstPtrWrapperIterator childScopesEnd() const { return m_childScopes.constEnd(); }
- void setInlineComponentName(const QString &inlineComponentName)
- {
- Q_ASSERT(isInlineComponent());
- m_inlineComponentName = inlineComponentName;
- }
+ void setInlineComponentName(const QString &inlineComponentName);
std::optional<QString> inlineComponentName() const;
InlineComponentOrDocumentRootName enclosingInlineComponentName() const;
- QVector<QQmlJSScope::Ptr> childScopes()
- {
- return m_childScopes;
- }
+ QVector<QQmlJSScope::Ptr> childScopes();
- QVector<QQmlJSScope::ConstPtr> childScopes() const
- {
- QVector<QQmlJSScope::ConstPtr> result;
- result.reserve(m_childScopes.size());
- for (const auto &child : m_childScopes)
- result.append(child);
- return result;
- }
+ QVector<QQmlJSScope::ConstPtr> childScopes() const;
static QTypeRevision resolveTypes(
- const Ptr &self, const QQmlJSScope::ContextualTypes &contextualTypes,
+ const Ptr &self, const QQmlJS::ContextualTypes &contextualTypes,
QSet<QString> *usedTypes = nullptr);
static void resolveNonEnumTypes(
- const QQmlJSScope::Ptr &self, const QQmlJSScope::ContextualTypes &contextualTypes,
+ const QQmlJSScope::Ptr &self, const QQmlJS::ContextualTypes &contextualTypes,
QSet<QString> *usedTypes = nullptr);
static void resolveEnums(
- const QQmlJSScope::Ptr &self, const QQmlJSScope::ConstPtr &intType);
+ const QQmlJSScope::Ptr &self, const QQmlJS::ContextualTypes &contextualTypes,
+ QSet<QString> *usedTypes = nullptr);
static void resolveList(
const QQmlJSScope::Ptr &self, const QQmlJSScope::ConstPtr &arrayType);
static void resolveGeneralizedGroup(
const QQmlJSScope::Ptr &self, const QQmlJSScope::ConstPtr &baseType,
- const QQmlJSScope::ContextualTypes &contextualTypes,
+ const QQmlJS::ContextualTypes &contextualTypes,
QSet<QString> *usedTypes = nullptr);
- void setSourceLocation(const QQmlJS::SourceLocation &sourceLocation)
- {
- m_sourceLocation = sourceLocation;
- }
-
- QQmlJS::SourceLocation sourceLocation() const
- {
- return m_sourceLocation;
- }
-
- static QQmlJSScope::ConstPtr nonCompositeBaseType(const QQmlJSScope::ConstPtr &type)
- {
- for (QQmlJSScope::ConstPtr base = type; base; base = base->baseType()) {
- if (!base->isComposite())
- return base;
- }
- return {};
- }
+ void setSourceLocation(const QQmlJS::SourceLocation &sourceLocation);
+ QQmlJS::SourceLocation sourceLocation() const;
- static QTypeRevision nonCompositeBaseRevision(const ImportedScope<QQmlJSScope::ConstPtr> &scope)
- {
- for (auto base = scope; base.scope;
- base = { base.scope->m_baseType.scope, base.scope->m_baseType.revision }) {
- if (!base.scope->isComposite())
- return base.revision;
- }
- return {};
- }
+ static QQmlJSScope::ConstPtr nonCompositeBaseType(const QQmlJSScope::ConstPtr &type);
- /*!
- \internal
- Checks whether \a otherScope is the same type as this.
+ static QTypeRevision
+ nonCompositeBaseRevision(const ImportedScope<QQmlJSScope::ConstPtr> &scope);
- In addition to checking whether the scopes are identical, we also cover duplicate scopes with
- the same internal name.
- */
- bool isSameType(const QQmlJSScope::ConstPtr &otherScope) const
- {
- return this == otherScope.get()
- || (!this->internalName().isEmpty()
- && this->internalName() == otherScope->internalName());
- }
+ bool isSameType(const QQmlJSScope::ConstPtr &otherScope) const;
+ bool inherits(const QQmlJSScope::ConstPtr &base) const;
+ bool canAssign(const QQmlJSScope::ConstPtr &derived) const;
- bool inherits(const QQmlJSScope::ConstPtr &base) const
- {
- for (const QQmlJSScope *scope = this; scope; scope = scope->baseType().get()) {
- if (scope->isSameType(base))
- return true;
- }
- return false;
- }
+ bool isInCustomParserParent() const;
- /*!
- \internal
- Checks whether \a derived type can be assigned to this type. Returns \c
- true if the type hierarchy of \a derived contains a type equal to this.
- \note Assigning \a derived to "QVariant" or "QJSValue" is always possible and
- the function returns \c true in this case. In addition any "QObject" based \a derived type
- can be assigned to a this type if that type is derived from "QQmlComponent".
- */
- bool canAssign(const QQmlJSScope::ConstPtr &derived) const;
+ static ImportedScope<QQmlJSScope::ConstPtr> findType(const QString &name,
+ const QQmlJS::ContextualTypes &contextualTypes,
+ QSet<QString> *usedTypes = nullptr);
- /*!
- \internal
- Checks whether this type or its parents have a custom parser.
- */
- bool isInCustomParserParent() const;
+ static QQmlSA::Element createQQmlSAElement(const ConstPtr &);
+ static QQmlSA::Element createQQmlSAElement(ConstPtr &&);
+ static const QQmlJSScope::ConstPtr &scope(const QQmlSA::Element &);
+ static constexpr qsizetype sizeofQQmlSAElement() { return QQmlSA::Element::sizeofElement; }
+private:
/*! \internal
- Minimal information about a QQmlJSMetaPropertyBinding that allows it to
- be manipulated similarly to QmlIR::Binding.
- */
+ Minimal information about a QQmlJSMetaPropertyBinding that allows it to
+ be manipulated similarly to QmlIR::Binding.
+ */
+ template <typename T>
+ friend class QTypeInfo; // so that we can Q_DECLARE_TYPEINFO QmlIRCompatibilityBindingData
struct QmlIRCompatibilityBindingData
{
QmlIRCompatibilityBindingData() = default;
@@ -699,31 +476,20 @@ QT_WARNING_POP
quint32 sourceLocationOffset = 0; // binding's source location offset
};
- /*! \internal
- * Finds a type in contextualTypes with given name.
- * If a type is found, then its name is inserted into usedTypes (when provided).
- * If contextualTypes has mode INTERNAl, then namespace resolution for enums is
- * done (eg for Qt::Alignment).
- * If contextualTypes has mode QML, then inline component resolution is done
- * ("qmlFileName.IC" is correctly resolved from qmlFileName).
- */
- static ImportedScope<QQmlJSScope::ConstPtr> findType(const QString &name,
- const ContextualTypes &contextualTypes,
- QSet<QString> *usedTypes = nullptr);
-
-private:
QQmlJSScope() = default;
QQmlJSScope(const QQmlJSScope &) = default;
QQmlJSScope &operator=(const QQmlJSScope &) = default;
static QTypeRevision resolveType(
- const QQmlJSScope::Ptr &self, const ContextualTypes &contextualTypes,
+ const QQmlJSScope::Ptr &self, const QQmlJS::ContextualTypes &contextualTypes,
QSet<QString> *usedTypes);
static void updateChildScope(
const QQmlJSScope::Ptr &childScope, const QQmlJSScope::Ptr &self,
- const QQmlJSScope::ContextualTypes &contextualTypes, QSet<QString> *usedTypes);
+ const QQmlJS::ContextualTypes &contextualTypes, QSet<QString> *usedTypes);
void addOwnPropertyBindingInQmlIROrder(const QQmlJSMetaPropertyBinding &binding,
BindingTargetSpecifier specifier);
+ bool hasCreatableFlag() const { return m_flags & Creatable; }
+ bool hasStructuredFlag() const { return m_flags & Structured; }
QHash<QString, JavaScriptIdentifier> m_jsIdentifiers;
@@ -752,7 +518,8 @@ private:
// the only relation between two types where the revisions matter.
ImportedScope<QQmlJSScope::WeakConstPtr> m_baseType;
- ScopeType m_scopeType = QMLScope;
+ ScopeType m_scopeType = ScopeType::QMLScope;
+ QStringList m_aliases;
QStringList m_interfaceNames;
QStringList m_ownDeferredNames;
QStringList m_ownImmediateNames;
@@ -790,22 +557,97 @@ private:
QQmlJS::SourceLocation m_sourceLocation;
- QString m_qualifiedName;
QString m_moduleName;
std::optional<QString> m_inlineComponentName;
};
+
+inline QQmlJSScope::Ptr QQmlJSScope::parentScope()
+{
+ return m_parentScope.toStrongRef();
+}
+
+inline QQmlJSScope::ConstPtr QQmlJSScope::parentScope() const
+{
+ QT_WARNING_PUSH
+#if defined(Q_CC_GNU_ONLY) && Q_CC_GNU < 1400 && Q_CC_GNU >= 1200
+ QT_WARNING_DISABLE_GCC("-Wuse-after-free")
+#endif
+ return QQmlJSScope::WeakConstPtr(m_parentScope).toStrongRef();
+ QT_WARNING_POP
+}
+
+inline QMultiHash<QString, QQmlJSMetaPropertyBinding> QQmlJSScope::ownPropertyBindings() const
+{
+ return m_propertyBindings;
+}
+
+inline QPair<QMultiHash<QString, QQmlJSMetaPropertyBinding>::const_iterator, QMultiHash<QString, QQmlJSMetaPropertyBinding>::const_iterator> QQmlJSScope::ownPropertyBindings(const QString &name) const
+{
+ return m_propertyBindings.equal_range(name);
+}
+
+inline bool QQmlJSScope::hasOwnPropertyBindings(const QString &name) const
+{
+ return m_propertyBindings.contains(name);
+}
+
+inline QQmlJSMetaMethod::AbsoluteFunctionIndex QQmlJSScope::ownRuntimeFunctionIndex(QQmlJSMetaMethod::RelativeFunctionIndex index) const
+{
+ const int i = static_cast<int>(index);
+ Q_ASSERT(i >= 0);
+ Q_ASSERT(i < int(m_runtimeFunctionIndices.size()));
+ return m_runtimeFunctionIndices[i];
+}
+
+inline void QQmlJSScope::setHasCustomParser(bool v)
+{
+ m_flags.setFlag(CustomParser, v);;
+}
+
+inline void QQmlJSScope::setInlineComponentName(const QString &inlineComponentName)
+{
+ Q_ASSERT(isInlineComponent());
+ m_inlineComponentName = inlineComponentName;
+}
+
+inline QVector<QQmlJSScope::Ptr> QQmlJSScope::childScopes()
+{
+ return m_childScopes;
+}
+
+inline void QQmlJSScope::setSourceLocation(const QQmlJS::SourceLocation &sourceLocation)
+{
+ m_sourceLocation = sourceLocation;
+}
+
+inline QQmlJS::SourceLocation QQmlJSScope::sourceLocation() const
+{
+ return m_sourceLocation;
+}
+
+inline QQmlJSScope::ConstPtr QQmlJSScope::nonCompositeBaseType(const ConstPtr &type)
+{
+ for (QQmlJSScope::ConstPtr base = type; base; base = base->baseType()) {
+ if (!base->isComposite())
+ return base;
+ }
+ return {};
+}
+
Q_DECLARE_TYPEINFO(QQmlJSScope::QmlIRCompatibilityBindingData, Q_RELOCATABLE_TYPE);
template<>
-class Q_QMLCOMPILER_PRIVATE_EXPORT QDeferredFactory<QQmlJSScope>
+class Q_QMLCOMPILER_EXPORT QDeferredFactory<QQmlJSScope>
{
public:
+ using TypeReader = std::function<QList<QQmlJS::DiagnosticMessage>(
+ QQmlJSImporter *importer, const QString &filePath,
+ const QSharedPointer<QQmlJSScope> &scopeToPopulate)>;
QDeferredFactory() = default;
- QDeferredFactory(QQmlJSImporter *importer, const QString &filePath) :
- m_filePath(filePath), m_importer(importer)
- {}
+ QDeferredFactory(QQmlJSImporter *importer, const QString &filePath,
+ const TypeReader &typeReader = {});
bool isValid() const
{
@@ -817,12 +659,15 @@ public:
return QFileInfo(m_filePath).baseName();
}
+ QString filePath() const { return m_filePath; }
+
+ QQmlJSImporter* importer() const { return m_importer; }
+
void setIsSingleton(bool isSingleton)
{
m_isSingleton = isSingleton;
}
- void setQualifiedName(const QString &qualifiedName) { m_qualifiedName = qualifiedName; }
void setModuleName(const QString &moduleName) { m_moduleName = moduleName; }
private:
@@ -837,18 +682,13 @@ private:
QString m_filePath;
QQmlJSImporter *m_importer = nullptr;
bool m_isSingleton = false;
- QString m_qualifiedName;
QString m_moduleName;
+ TypeReader m_typeReader;
};
using QQmlJSExportedScope = QQmlJSScope::ExportedScope<QQmlJSScope::Ptr>;
using QQmlJSImportedScope = QQmlJSScope::ImportedScope<QQmlJSScope::ConstPtr>;
-struct QQmlJSTypeInfo
-{
- QMultiHash<QQmlJSScope::ConstPtr, QQmlJSScope::ConstPtr> usedAttachedTypes;
-};
-
QT_END_NAMESPACE
#endif // QQMLJSSCOPE_P_H
diff --git a/src/qmlcompiler/qqmljsscopesbyid_p.h b/src/qmlcompiler/qqmljsscopesbyid_p.h
index 77ad1947ab..c9dca9ae34 100644
--- a/src/qmlcompiler/qqmljsscopesbyid_p.h
+++ b/src/qmlcompiler/qqmljsscopesbyid_p.h
@@ -22,6 +22,12 @@
QT_BEGIN_NAMESPACE
+enum QQmlJSScopesByIdOption: char {
+ Default = 0,
+ AssumeComponentsAreBound = 1,
+};
+Q_DECLARE_FLAGS(QQmlJSScopesByIdOptions, QQmlJSScopesByIdOption);
+
class QQmlJSScopesById
{
public:
@@ -31,13 +37,15 @@ public:
void setSignaturesAreEnforced(bool enforced) { m_signaturesAreEnforced = enforced; }
bool signaturesAreEnforced() const { return m_signaturesAreEnforced; }
- void setValueTypesAreCopied(bool copied) { m_valueTypesAreCopied = copied; }
- bool valueTypesAreCopied() const { return m_valueTypesAreCopied; }
+ void setValueTypesAreAddressable(bool addressable) { m_valueTypesAreAddressable = addressable; }
+ bool valueTypesAreAddressable() const { return m_valueTypesAreAddressable; }
- QString id(const QQmlJSScope::ConstPtr &scope) const
+ QString id(const QQmlJSScope::ConstPtr &scope, const QQmlJSScope::ConstPtr &referrer,
+ QQmlJSScopesByIdOptions options = Default) const
{
+ const QQmlJSScope::ConstPtr referrerRoot = componentRoot(referrer);
for (auto it = m_scopesById.begin(), end = m_scopesById.end(); it != end; ++it) {
- if (*it == scope)
+ if (*it == scope && isComponentVisible(componentRoot(*it), referrerRoot, options))
return it.key();
}
return QString();
@@ -48,7 +56,8 @@ public:
Returns the scope that has id \a id in the component to which \a referrer belongs to.
If no such scope exists, a null scope is returned.
*/
- QQmlJSScope::ConstPtr scope(const QString &id, const QQmlJSScope::ConstPtr &referrer) const
+ QQmlJSScope::ConstPtr scope(const QString &id, const QQmlJSScope::ConstPtr &referrer,
+ QQmlJSScopesByIdOptions options = Default) const
{
Q_ASSERT(!id.isEmpty());
const auto range = m_scopesById.equal_range(id);
@@ -57,7 +66,7 @@ public:
const QQmlJSScope::ConstPtr referrerRoot = componentRoot(referrer);
for (auto it = range.first; it != range.second; ++it) {
- if (isComponentVisible(componentRoot(*it), referrerRoot))
+ if (isComponentVisible(componentRoot(*it), referrerRoot, options))
return *it;
}
@@ -94,10 +103,11 @@ private:
return scope;
}
- bool isComponentVisible(
- const QQmlJSScope::ConstPtr &observed, const QQmlJSScope::ConstPtr &observer) const
+ bool isComponentVisible(const QQmlJSScope::ConstPtr &observed,
+ const QQmlJSScope::ConstPtr &observer,
+ QQmlJSScopesByIdOptions options) const
{
- if (!m_componentsAreBound)
+ if (!m_componentsAreBound && !options.testAnyFlag(AssumeComponentsAreBound))
return observed == observer;
for (QQmlJSScope::ConstPtr scope = observer; scope; scope = scope->parentScope()) {
@@ -111,7 +121,7 @@ private:
QMultiHash<QString, QQmlJSScope::ConstPtr> m_scopesById;
bool m_componentsAreBound = false;
bool m_signaturesAreEnforced = true;
- bool m_valueTypesAreCopied = true;
+ bool m_valueTypesAreAddressable = false;
};
QT_END_NAMESPACE
diff --git a/src/qmlcompiler/qqmljsshadowcheck.cpp b/src/qmlcompiler/qqmljsshadowcheck.cpp
index 7ccef5e7bb..29de04e2f1 100644
--- a/src/qmlcompiler/qqmljsshadowcheck.cpp
+++ b/src/qmlcompiler/qqmljsshadowcheck.cpp
@@ -26,17 +26,32 @@ using namespace Qt::StringLiterals;
* 3. The property is declared final.
* 4. The object we are retrieving the property from is a value type. Value
* types cannot be used polymorphically.
+ *
+ * If the property is potentially shadowed, we can still retrieve it, but we
+ * don't know its type. We should assume "var" then.
+ *
+ * All of the above also holds for methods. There we have to transform the
+ * arguments and return types into "var".
*/
-void QQmlJSShadowCheck::run(
- const InstructionAnnotations *annotations, const Function *function,
- QQmlJS::DiagnosticMessage *error)
+QQmlJSCompilePass::BlocksAndAnnotations QQmlJSShadowCheck::run(const Function *function,
+ QQmlJS::DiagnosticMessage *error)
{
- m_annotations = annotations;
m_function = function;
m_error = error;
m_state = initialState(function);
decode(m_function->code.constData(), static_cast<uint>(m_function->code.size()));
+
+ for (const auto &store : m_resettableStores)
+ checkResettable(store.accumulatorIn, store.instructionOffset);
+
+ // Re-check all base types. We may have made them var after detecting them.
+ for (const auto &base : m_baseTypes) {
+ if (checkBaseType(base) == Shadowable)
+ break;
+ }
+
+ return { std::move(m_basicBlocks), std::move(m_annotations) };
}
void QQmlJSShadowCheck::generate_LoadProperty(int nameIndex)
@@ -45,8 +60,11 @@ void QQmlJSShadowCheck::generate_LoadProperty(int nameIndex)
return; // enum lookup cannot be shadowed.
auto accumulatorIn = m_state.registers.find(Accumulator);
- if (accumulatorIn != m_state.registers.end())
- checkShadowing(accumulatorIn.value().content, m_jsUnitGenerator->stringForIndex(nameIndex));
+ if (accumulatorIn != m_state.registers.end()) {
+ checkShadowing(
+ accumulatorIn.value().content, m_jsUnitGenerator->stringForIndex(nameIndex),
+ Accumulator);
+ }
}
void QQmlJSShadowCheck::generate_GetLookup(int index)
@@ -55,23 +73,70 @@ void QQmlJSShadowCheck::generate_GetLookup(int index)
return; // enum lookup cannot be shadowed.
auto accumulatorIn = m_state.registers.find(Accumulator);
- if (accumulatorIn != m_state.registers.end())
- checkShadowing(accumulatorIn.value().content, m_jsUnitGenerator->lookupName(index));
+ if (accumulatorIn != m_state.registers.end()) {
+ checkShadowing(
+ accumulatorIn.value().content, m_jsUnitGenerator->lookupName(index), Accumulator);
+ }
+}
+
+void QQmlJSShadowCheck::generate_GetOptionalLookup(int index, int offset)
+{
+ Q_UNUSED(offset);
+ generate_GetLookup(index);
+}
+
+void QQmlJSShadowCheck::handleStore(int base, const QString &memberName)
+{
+ const int instructionOffset = currentInstructionOffset();
+ const QQmlJSRegisterContent &readAccumulator
+ = m_annotations[instructionOffset].readRegisters[Accumulator].content;
+ const auto baseType = m_state.registers[base].content;
+
+ // If the accumulator is already read as var, we don't have to do anything.
+ if (m_typeResolver->registerContains(readAccumulator, m_typeResolver->varType())) {
+ if (checkBaseType(baseType) == NotShadowable)
+ m_baseTypes.append(baseType);
+ return;
+ }
+
+ if (checkShadowing(baseType, memberName, base) == Shadowable)
+ return;
+
+ // If the property isn't shadowable, 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())
+ m_resettableStores.append({m_state.accumulatorIn(), instructionOffset});
}
void QQmlJSShadowCheck::generate_StoreProperty(int nameIndex, int base)
{
- checkShadowing(m_state.registers[base].content, m_jsUnitGenerator->stringForIndex(nameIndex));
+ handleStore(base, m_jsUnitGenerator->stringForIndex(nameIndex));
}
void QQmlJSShadowCheck::generate_SetLookup(int index, int base)
{
- checkShadowing(m_state.registers[base].content, m_jsUnitGenerator->lookupName(index));
+ handleStore(base, m_jsUnitGenerator->lookupName(index));
+}
+
+void QQmlJSShadowCheck::generate_CallProperty(int nameIndex, int base, int argc, int argv)
+{
+ Q_UNUSED(argc);
+ Q_UNUSED(argv);
+ checkShadowing(m_state.registers[base].content, m_jsUnitGenerator->lookupName(nameIndex), base);
+}
+
+void QQmlJSShadowCheck::generate_CallPropertyLookup(int nameIndex, int base, int argc, int argv)
+{
+ Q_UNUSED(argc);
+ Q_UNUSED(argv);
+ checkShadowing(m_state.registers[base].content, m_jsUnitGenerator->lookupName(nameIndex), base);
}
QV4::Moth::ByteCodeHandler::Verdict QQmlJSShadowCheck::startInstruction(QV4::Moth::Instr::Type)
{
- m_state = nextStateFromAnnotations(m_state, *m_annotations);
+ m_state = nextStateFromAnnotations(m_state, m_annotations);
return (m_state.hasSideEffects() || m_state.changedRegisterIndex() != InvalidRegister)
? ProcessInstruction
: SkipInstruction;
@@ -81,17 +146,24 @@ void QQmlJSShadowCheck::endInstruction(QV4::Moth::Instr::Type)
{
}
-void QQmlJSShadowCheck::checkShadowing(
- const QQmlJSRegisterContent &baseType, const QString &memberName)
+QQmlJSShadowCheck::Shadowability QQmlJSShadowCheck::checkShadowing(
+ const QQmlJSRegisterContent &baseType, const QString &memberName, int baseRegister)
{
- if (baseType.storedType()->accessSemantics() != QQmlJSScope::AccessSemantics::Reference)
- return;
+ if (checkBaseType(baseType) == Shadowable)
+ return Shadowable;
+ else
+ m_baseTypes.append(baseType);
+
+ if (!baseType.containedType()->isReferenceType())
+ return NotShadowable;
switch (baseType.variant()) {
- case QQmlJSRegisterContent::ObjectProperty:
case QQmlJSRegisterContent::ExtensionObjectProperty:
+ case QQmlJSRegisterContent::ExtensionScopeProperty:
+ case QQmlJSRegisterContent::MethodReturnValue:
+ case QQmlJSRegisterContent::ObjectProperty:
case QQmlJSRegisterContent::ScopeProperty:
- case QQmlJSRegisterContent::ExtensionScopeProperty: {
+ case QQmlJSRegisterContent::Unknown: {
const QQmlJSRegisterContent member = m_typeResolver->memberType(baseType, memberName);
// You can have something like parent.QtQuick.Screen.pixelDensity
@@ -100,26 +172,73 @@ void QQmlJSShadowCheck::checkShadowing(
// those are not shadowable.
if (!member.isValid()) {
Q_ASSERT(m_typeResolver->isPrefix(memberName));
- return;
+ return NotShadowable;
}
if (member.isProperty()) {
if (member.property().isFinal())
- return; // final properties can't be shadowed
+ return NotShadowable; // final properties can't be shadowed
} else if (!member.isMethod()) {
- return; // Only properties and methods can be shadowed
+ return NotShadowable; // Only properties and methods can be shadowed
}
- setError(u"Member %1 of %2 can be shadowed"_s
- .arg(memberName, m_state.accumulatorIn().descriptiveName()));
- return;
+ m_logger->log(
+ u"Member %1 of %2 can be shadowed"_s.arg(memberName, baseType.descriptiveName()),
+ qmlCompiler, currentSourceLocation());
+
+ // Make it "var". We don't know what it is.
+ const QQmlJSScope::ConstPtr varType = m_typeResolver->varType();
+ const QQmlJSRegisterContent varContent = m_typeResolver->globalType(varType);
+ InstructionAnnotation &currentAnnotation = m_annotations[currentInstructionOffset()];
+
+ if (currentAnnotation.changedRegisterIndex != InvalidRegister) {
+ m_typeResolver->adjustOriginalType(
+ currentAnnotation.changedRegister.containedType(), varType);
+ m_adjustedTypes.insert(currentAnnotation.changedRegister);
+ }
+
+ for (auto it = currentAnnotation.readRegisters.begin(),
+ end = currentAnnotation.readRegisters.end();
+ it != end; ++it) {
+ if (it.key() != baseRegister)
+ it->second.content = m_typeResolver->convert(it->second.content, varContent);
+ }
+ return Shadowable;
}
default:
// In particular ObjectById is fine as that cannot change into something else
// Singleton should also be fine, unless the factory function creates an object
// 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);
+}
+
+QQmlJSShadowCheck::Shadowability QQmlJSShadowCheck::checkBaseType(
+ const QQmlJSRegisterContent &baseType)
+{
+ if (!m_adjustedTypes.contains(baseType))
+ return NotShadowable;
+ setError(u"Cannot use shadowable base type for further lookups: %1"_s.arg(baseType.descriptiveName()));
+ return Shadowable;
+}
+
QT_END_NAMESPACE
diff --git a/src/qmlcompiler/qqmljsshadowcheck_p.h b/src/qmlcompiler/qqmljsshadowcheck_p.h
index 92990a9eeb..dfa00134cb 100644
--- a/src/qmlcompiler/qqmljsshadowcheck_p.h
+++ b/src/qmlcompiler/qqmljsshadowcheck_p.h
@@ -18,31 +18,50 @@
QT_BEGIN_NAMESPACE
-class Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSShadowCheck : public QQmlJSCompilePass
+class Q_QMLCOMPILER_EXPORT QQmlJSShadowCheck : public QQmlJSCompilePass
{
public:
QQmlJSShadowCheck(const QV4::Compiler::JSUnitGenerator *jsUnitGenerator,
- const QQmlJSTypeResolver *typeResolver, QQmlJSLogger *logger)
- : QQmlJSCompilePass(jsUnitGenerator, typeResolver, logger)
+ const QQmlJSTypeResolver *typeResolver, QQmlJSLogger *logger,
+ BasicBlocks basicBlocks, InstructionAnnotations annotations)
+ : QQmlJSCompilePass(jsUnitGenerator, typeResolver, logger, basicBlocks, annotations)
{}
~QQmlJSShadowCheck() = default;
- void run(const InstructionAnnotations *annotations, const Function *function,
- QQmlJS::DiagnosticMessage *error);
+ BlocksAndAnnotations run(const Function *function, QQmlJS::DiagnosticMessage *error);
private:
+ struct ResettableStore {
+ QQmlJSRegisterContent accumulatorIn;
+ int instructionOffset = -1;
+ };
+
+ void handleStore(int base, const QString &memberName);
+
void generate_LoadProperty(int nameIndex) override;
void generate_GetLookup(int index) override;
+ void generate_GetOptionalLookup(int index, int offset) override;
void generate_StoreProperty(int nameIndex, int base) override;
void generate_SetLookup(int index, int base) override;
+ void generate_CallProperty(int nameIndex, int base, int argc, int argv) override;
+ void generate_CallPropertyLookup(int nameIndex, int base, int argc, int argv) override;
QV4::Moth::ByteCodeHandler::Verdict startInstruction(QV4::Moth::Instr::Type) override;
void endInstruction(QV4::Moth::Instr::Type) override;
- void checkShadowing(const QQmlJSRegisterContent &baseType, const QString &propertyName);
+ enum Shadowability { NotShadowable, Shadowable };
+ Shadowability checkShadowing(
+ const QQmlJSRegisterContent &baseType, const QString &propertyName, int baseRegister);
+
+ void checkResettable(const QQmlJSRegisterContent &accumulatorIn, int instructionOffset);
+
+ Shadowability checkBaseType(const QQmlJSRegisterContent &baseType);
+
+ QList<ResettableStore> m_resettableStores;
+ QList<QQmlJSRegisterContent> m_baseTypes;
+ QSet<QQmlJSRegisterContent> m_adjustedTypes;
- const InstructionAnnotations *m_annotations = nullptr;
State m_state;
};
diff --git a/src/qmlcompiler/qqmljsstoragegeneralizer.cpp b/src/qmlcompiler/qqmljsstoragegeneralizer.cpp
index 543dec9ff6..3ba709b21f 100644
--- a/src/qmlcompiler/qqmljsstoragegeneralizer.cpp
+++ b/src/qmlcompiler/qqmljsstoragegeneralizer.cpp
@@ -19,20 +19,20 @@ QT_BEGIN_NAMESPACE
* operates only on the annotations and the function description.
*/
-QQmlJSCompilePass::InstructionAnnotations QQmlJSStorageGeneralizer::run(
- InstructionAnnotations annotations, Function *function,
- QQmlJS::DiagnosticMessage *error)
+QQmlJSCompilePass::BlocksAndAnnotations
+QQmlJSStorageGeneralizer::run(Function *function, QQmlJS::DiagnosticMessage *error)
{
+ m_function = function;
m_error = error;
- if (QQmlJSScope::ConstPtr &returnType = function->returnType) {
+ if (QQmlJSRegisterContent &returnType = function->returnType; returnType.isValid()) {
if (QQmlJSScope::ConstPtr stored = m_typeResolver->genericType(
- returnType, QQmlJSTypeResolver::ComponentIsGeneric::Yes)) {
- returnType = stored;
+ returnType.storedType(), QQmlJSTypeResolver::ComponentIsGeneric::Yes)) {
+ returnType = returnType.storedIn(stored);
} else {
setError(QStringLiteral("Cannot store the return type %1.")
- .arg(returnType->internalName(), 0));
- return InstructionAnnotations();
+ .arg(returnType.storedType()->internalName()));
+ return {};
}
}
@@ -53,12 +53,12 @@ QQmlJSCompilePass::InstructionAnnotations QQmlJSStorageGeneralizer::run(
transformRegister(argument);
}
- for (auto i = annotations.begin(), iEnd = annotations.end(); i != iEnd; ++i) {
+ for (auto i = m_annotations.begin(), iEnd = m_annotations.end(); i != iEnd; ++i) {
transformRegister(i->second.changedRegister);
transformRegisters(i->second.typeConversions);
}
- return annotations;
+ return { std::move(m_basicBlocks), std::move(m_annotations) };
}
QT_END_NAMESPACE
diff --git a/src/qmlcompiler/qqmljsstoragegeneralizer_p.h b/src/qmlcompiler/qqmljsstoragegeneralizer_p.h
index d04e5ce1dd..9ef4699fca 100644
--- a/src/qmlcompiler/qqmljsstoragegeneralizer_p.h
+++ b/src/qmlcompiler/qqmljsstoragegeneralizer_p.h
@@ -18,16 +18,16 @@
QT_BEGIN_NAMESPACE
-class Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSStorageGeneralizer : public QQmlJSCompilePass
+class Q_QMLCOMPILER_EXPORT QQmlJSStorageGeneralizer : public QQmlJSCompilePass
{
public:
QQmlJSStorageGeneralizer(const QV4::Compiler::JSUnitGenerator *jsUnitGenerator,
- const QQmlJSTypeResolver *typeResolver, QQmlJSLogger *logger)
- : QQmlJSCompilePass(jsUnitGenerator, typeResolver, logger)
+ const QQmlJSTypeResolver *typeResolver, QQmlJSLogger *logger,
+ BasicBlocks basicBlocks, InstructionAnnotations annotations)
+ : QQmlJSCompilePass(jsUnitGenerator, typeResolver, logger, basicBlocks, annotations)
{}
- InstructionAnnotations run(InstructionAnnotations annotations, Function *function,
- QQmlJS::DiagnosticMessage *error);
+ BlocksAndAnnotations run(Function *function, QQmlJS::DiagnosticMessage *error);
protected:
// We don't have to use the byte code here. We only transform the instruction annotations.
diff --git a/src/qmlcompiler/qqmljsstorageinitializer.cpp b/src/qmlcompiler/qqmljsstorageinitializer.cpp
new file mode 100644
index 0000000000..b0f7d1a49f
--- /dev/null
+++ b/src/qmlcompiler/qqmljsstorageinitializer.cpp
@@ -0,0 +1,81 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "qqmljsstorageinitializer_p.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ * \internal
+ * \class QQmlJSStorageInitializer
+ *
+ * The QQmlJSStorageInitializer is a compile pass that initializes the storage
+ * for all register contents.
+ *
+ * QQmlJSStorageInitializer does not have to use the byte code at all but
+ * operates only on the annotations and the function description.
+ */
+
+QQmlJSCompilePass::BlocksAndAnnotations
+QQmlJSStorageInitializer::run(Function *function, QQmlJS::DiagnosticMessage *error)
+{
+ m_function = function;
+ m_error = error;
+
+ if (QQmlJSRegisterContent &returnType = function->returnType; returnType.isValid()) {
+ if (const QQmlJSScope::ConstPtr stored
+ = m_typeResolver->storedType(returnType.containedType())) {
+ returnType = returnType.storedIn(m_typeResolver->trackedType(stored));
+ } else {
+ setError(QStringLiteral("Cannot store the return type %1.")
+ .arg(returnType.containedType()->internalName()));
+ return {};
+ }
+ }
+
+ const auto storeRegister = [&](QQmlJSRegisterContent &content) {
+ if (!content.isValid())
+ return;
+
+ const QQmlJSScope::ConstPtr original
+ = m_typeResolver->originalType(content.containedType());
+ const QQmlJSScope::ConstPtr originalStored = m_typeResolver->storedType(original);
+ const QQmlJSScope::ConstPtr originalTracked = m_typeResolver->trackedType(originalStored);
+ content = content.storedIn(originalTracked);
+
+ const QQmlJSScope::ConstPtr adjustedStored
+ = m_typeResolver->storedType(content.containedType());
+
+ if (!m_typeResolver->adjustTrackedType(originalTracked, adjustedStored)) {
+ setError(QStringLiteral("Cannot adjust stored type for %1.")
+ .arg(content.containedType()->internalName()));
+ }
+ };
+
+ const auto storeRegisters = [&](VirtualRegisters &registers) {
+ for (auto j = registers.begin(), jEnd = registers.end(); j != jEnd; ++j)
+ storeRegister(j.value().content);
+ };
+
+ storeRegister(function->qmlScope);
+
+ for (QQmlJSRegisterContent &argument : function->argumentTypes) {
+ Q_ASSERT(argument.isValid());
+ storeRegister(argument);
+ }
+
+ for (QQmlJSRegisterContent &argument : function->registerTypes) {
+ Q_ASSERT(argument.isValid());
+ storeRegister(argument);
+ }
+
+ for (auto i = m_annotations.begin(), iEnd = m_annotations.end(); i != iEnd; ++i) {
+ storeRegister(i->second.changedRegister);
+ storeRegisters(i->second.typeConversions);
+ storeRegisters(i->second.readRegisters);
+ }
+
+ return { std::move(m_basicBlocks), std::move(m_annotations) };
+}
+
+QT_END_NAMESPACE
diff --git a/src/qmlcompiler/qqmljsstorageinitializer_p.h b/src/qmlcompiler/qqmljsstorageinitializer_p.h
new file mode 100644
index 0000000000..0644807543
--- /dev/null
+++ b/src/qmlcompiler/qqmljsstorageinitializer_p.h
@@ -0,0 +1,40 @@
+// 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 QQMLJSSTORAGEINITIALIZER_P_H
+#define QQMLJSSTORAGEINITIALIZER_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#include <private/qqmljscompilepass_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class Q_QMLCOMPILER_EXPORT QQmlJSStorageInitializer : public QQmlJSCompilePass
+{
+public:
+ QQmlJSStorageInitializer(const QV4::Compiler::JSUnitGenerator *jsUnitGenerator,
+ const QQmlJSTypeResolver *typeResolver, QQmlJSLogger *logger,
+ BasicBlocks basicBlocks, InstructionAnnotations annotations)
+ : QQmlJSCompilePass(jsUnitGenerator, typeResolver, logger, basicBlocks, annotations)
+ {}
+
+ BlocksAndAnnotations run(Function *function, QQmlJS::DiagnosticMessage *error);
+
+protected:
+ // We don't have to use the byte code here. We only transform the instruction annotations.
+ Verdict startInstruction(QV4::Moth::Instr::Type) override { return SkipInstruction; }
+ void endInstruction(QV4::Moth::Instr::Type) override {}
+};
+
+QT_END_NAMESPACE
+
+#endif // QQMLJSSTORAGEINITIALIZER_P_H
diff --git a/src/qmlcompiler/qqmljstypedescriptionreader.cpp b/src/qmlcompiler/qqmljstypedescriptionreader.cpp
index 8528d39f0f..061c561671 100644
--- a/src/qmlcompiler/qqmljstypedescriptionreader.cpp
+++ b/src/qmlcompiler/qqmljstypedescriptionreader.cpp
@@ -166,6 +166,7 @@ void QQmlJSTypeDescriptionReader::readDependencies(UiScriptBinding *ast)
void QQmlJSTypeDescriptionReader::readComponent(UiObjectDefinition *ast)
{
+ m_currentCtorIndex = 0;
QQmlJSScope::Ptr scope = QQmlJSScope::create();
QList<QQmlJSScope::Export> exports;
@@ -200,6 +201,8 @@ void QQmlJSTypeDescriptionReader::readComponent(UiObjectDefinition *ast)
scope->setOwnParentPropertyName(readStringBinding(script));
} else if (name == QLatin1String("exports")) {
exports = readExports(script);
+ } else if (name == QLatin1String("aliases")) {
+ readAliases(script, scope);
} else if (name == QLatin1String("interfaces")) {
readInterfaces(script, scope);
} else if (name == QLatin1String("exportMetaObjectRevisions")) {
@@ -212,6 +215,8 @@ void QQmlJSTypeDescriptionReader::readComponent(UiObjectDefinition *ast)
scope->setIsSingleton(readBoolBinding(script));
} else if (name == QLatin1String("isCreatable")) {
scope->setCreatableFlag(readBoolBinding(script));
+ } else if (name == QLatin1String("isStructured")) {
+ scope->setStructuredFlag(readBoolBinding(script));
} else if (name == QLatin1String("isComposite")) {
scope->setIsComposite(readBoolBinding(script));
} else if (name == QLatin1String("hasCustomParser")) {
@@ -232,6 +237,8 @@ void QQmlJSTypeDescriptionReader::readComponent(UiObjectDefinition *ast)
}
} else if (name == QLatin1String("extension")) {
scope->setExtensionTypeName(readStringBinding(script));
+ } else if (name == QLatin1String("extensionIsJavaScript")) {
+ scope->setExtensionIsJavaScript(readBoolBinding(script));
} else if (name == QLatin1String("extensionIsNamespace")) {
scope->setExtensionIsNamespace(readBoolBinding(script));
} else if (name == QLatin1String("deferredNames")) {
@@ -242,8 +249,9 @@ void QQmlJSTypeDescriptionReader::readComponent(UiObjectDefinition *ast)
addWarning(script->firstSourceLocation(),
tr("Expected only name, prototype, defaultProperty, attachedType, "
"valueType, exports, interfaces, isSingleton, isCreatable, "
- "isComposite, hasCustomParser, exportMetaObjectRevisions, "
- "deferredNames, and immediateNames in script bindings, not \"%1\".")
+ "isStructured, isComposite, hasCustomParser, aliases, "
+ "exportMetaObjectRevisions, deferredNames, and immediateNames "
+ "in script bindings, not \"%1\".")
.arg(name));
}
} else {
@@ -262,15 +270,15 @@ void QQmlJSTypeDescriptionReader::readComponent(UiObjectDefinition *ast)
m_objects->append({scope, exports});
}
-void QQmlJSTypeDescriptionReader::readSignalOrMethod(UiObjectDefinition *ast, bool isMethod,
- const QQmlJSScope::Ptr &scope)
+void QQmlJSTypeDescriptionReader::readSignalOrMethod(
+ UiObjectDefinition *ast, bool isMethod, const QQmlJSScope::Ptr &scope)
{
QQmlJSMetaMethod metaMethod;
// ### confusion between Method and Slot. Method should be removed.
if (isMethod)
- metaMethod.setMethodType(QQmlJSMetaMethod::Slot);
+ metaMethod.setMethodType(QQmlJSMetaMethodType::Slot);
else
- metaMethod.setMethodType(QQmlJSMetaMethod::Signal);
+ metaMethod.setMethodType(QQmlJSMetaMethodType::Signal);
for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) {
UiObjectMember *member = it->member;
@@ -293,22 +301,40 @@ void QQmlJSTypeDescriptionReader::readSignalOrMethod(UiObjectDefinition *ast, bo
} else if (name == QLatin1String("revision")) {
metaMethod.setRevision(readIntBinding(script));
} else if (name == QLatin1String("isCloned")) {
- metaMethod.setIsCloned(true);
+ metaMethod.setIsCloned(readBoolBinding(script));
} else if (name == QLatin1String("isConstructor")) {
- metaMethod.setIsConstructor(true);
+ // The constructors in the moc json output are ordered the same
+ // way as the ones in the metaobject. qmltyperegistrar moves them into
+ // the same list as the other members, but maintains their order.
+ if (readBoolBinding(script)) {
+ metaMethod.setIsConstructor(true);
+ metaMethod.setConstructorIndex(
+ QQmlJSMetaMethod::RelativeFunctionIndex(m_currentCtorIndex++));
+ }
} else if (name == QLatin1String("isJavaScriptFunction")) {
- metaMethod.setIsJavaScriptFunction(true);
+ metaMethod.setIsJavaScriptFunction(readBoolBinding(script));
} else if (name == QLatin1String("isList")) {
- // TODO: Theoretically this can happen. QQmlJSMetaMethod should store it.
+ auto metaReturnType = metaMethod.returnValue();
+ metaReturnType.setIsList(readBoolBinding(script));
+ metaMethod.setReturnValue(metaReturnType);
} else if (name == QLatin1String("isPointer")) {
// TODO: We don't need this information. We can probably drop all isPointer members
// once we make sure that the type information is always complete. The
// description of the type being referenced has access semantics after all.
+ auto metaReturnType = metaMethod.returnValue();
+ metaReturnType.setIsPointer(readBoolBinding(script));
+ metaMethod.setReturnValue(metaReturnType);
+ } else if (name == QLatin1String("isTypeConstant")) {
+ auto metaReturnType = metaMethod.returnValue();
+ metaReturnType.setTypeQualifier(readBoolBinding(script)
+ ? QQmlJSMetaParameter::Const
+ : QQmlJSMetaParameter::NonConst);
+ metaMethod.setReturnValue(metaReturnType);
} else {
addWarning(script->firstSourceLocation(),
- tr("Expected only name, type, revision, isPointer, isList, "
- "isCloned, isConstructor, and "
- "isJavaScriptFunction in script bindings."));
+ tr("Expected only name, type, revision, isPointer, isTypeConstant, "
+ "isList, isCloned, isConstructor, and isJavaScriptFunction "
+ "in script bindings."));
}
} else {
addWarning(member->firstSourceLocation(),
@@ -354,8 +380,10 @@ void QQmlJSTypeDescriptionReader::readProperty(UiObjectDefinition *ast, const QQ
property.setIsList(readBoolBinding(script));
} else if (id == QLatin1String("isFinal")) {
property.setIsFinal(readBoolBinding(script));
- } else if (id == QLatin1String("isConstant")) {
- property.setIsConstant(readBoolBinding(script));
+ } else if (id == QLatin1String("isTypeConstant")) {
+ property.setIsTypeConstant(readBoolBinding(script));
+ } else if (id == QLatin1String("isPropertyConstant")) {
+ property.setIsPropertyConstant(readBoolBinding(script));
} else if (id == QLatin1String("revision")) {
property.setRevision(readIntBinding(script));
} else if (id == QLatin1String("bindable")) {
@@ -374,8 +402,8 @@ void QQmlJSTypeDescriptionReader::readProperty(UiObjectDefinition *ast, const QQ
property.setPrivateClass(readStringBinding(script));
} else {
addWarning(script->firstSourceLocation(),
- tr("Expected only type, name, revision, isPointer, isReadonly, isRequired, "
- "isFinal, isList, bindable, read, write, reset, notify, index, and "
+ tr("Expected only type, name, revision, isPointer, isTypeConstant, isReadonly, isRequired, "
+ "isFinal, isList, bindable, read, write, isPropertyConstant, reset, notify, index, and "
"privateClass and script bindings."));
}
}
@@ -412,11 +440,13 @@ void QQmlJSTypeDescriptionReader::readEnum(UiObjectDefinition *ast, const QQmlJS
metaEnum.setIsFlag(readBoolBinding(script));
} else if (name == QLatin1String("values")) {
readEnumValues(script, &metaEnum);
- } else if (name == QLatin1String("scoped")) {
- metaEnum.setScoped(readBoolBinding(script));
+ } else if (name == QLatin1String("isScoped")) {
+ metaEnum.setIsScoped(readBoolBinding(script));
+ } else if (name == QLatin1String("type")) {
+ metaEnum.setTypeName(readStringBinding(script));
} else {
addWarning(script->firstSourceLocation(),
- tr("Expected only name and values script bindings."));
+ tr("Expected only name, alias, isFlag, values, isScoped, or type."));
}
}
@@ -429,6 +459,7 @@ void QQmlJSTypeDescriptionReader::readParameter(UiObjectDefinition *ast, QQmlJSM
QString type;
bool isConstant = false;
bool isPointer = false;
+ bool isList = false;
for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) {
UiObjectMember *member = it->member;
@@ -445,21 +476,23 @@ void QQmlJSTypeDescriptionReader::readParameter(UiObjectDefinition *ast, QQmlJSM
type = readStringBinding(script);
} else if (id == QLatin1String("isPointer")) {
isPointer = readBoolBinding(script);
- } else if (id == QLatin1String("isConstant")) {
+ } else if (id == QLatin1String("isTypeConstant")) {
isConstant = readBoolBinding(script);
} else if (id == QLatin1String("isReadonly")) {
// ### unhandled
} else if (id == QLatin1String("isList")) {
- // ### unhandled
+ isList = readBoolBinding(script);
} else {
addWarning(script->firstSourceLocation(),
- tr("Expected only name and type script bindings."));
+ tr("Expected only name, type, isPointer, isTypeConstant, isReadonly, "
+ "or IsList script bindings."));
}
}
QQmlJSMetaParameter p(name, type);
p.setTypeQualifier(isConstant ? QQmlJSMetaParameter::Const : QQmlJSMetaParameter::NonConst);
p.setIsPointer(isPointer);
+ p.setIsList(isList);
metaMethod->addParameter(std::move(p));
}
@@ -657,6 +690,12 @@ QList<QQmlJSScope::Export> QQmlJSTypeDescriptionReader::readExports(UiScriptBind
return exports;
}
+void QQmlJSTypeDescriptionReader::readAliases(
+ QQmlJS::AST::UiScriptBinding *ast, const QQmlJSScope::Ptr &scope)
+{
+ scope->setAliases(readStringList(ast));
+}
+
void QQmlJSTypeDescriptionReader::readInterfaces(UiScriptBinding *ast, const QQmlJSScope::Ptr &scope)
{
auto *arrayLit = getArray(ast);
diff --git a/src/qmlcompiler/qqmljstypedescriptionreader_p.h b/src/qmlcompiler/qqmljstypedescriptionreader_p.h
index 37dd388308..2bbae61fd6 100644
--- a/src/qmlcompiler/qqmljstypedescriptionreader_p.h
+++ b/src/qmlcompiler/qqmljstypedescriptionreader_p.h
@@ -4,7 +4,7 @@
#ifndef QQMLJSTYPEDESCRIPTIONREADER_P_H
#define QQMLJSTYPEDESCRIPTIONREADER_P_H
-#include <private/qtqmlcompilerexports_p.h>
+#include <qtqmlcompilerexports.h>
//
// W A R N I N G
@@ -25,7 +25,7 @@
QT_BEGIN_NAMESPACE
-class Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSTypeDescriptionReader
+class Q_QMLCOMPILER_EXPORT QQmlJSTypeDescriptionReader
{
Q_DECLARE_TR_FUNCTIONS(QQmlJSTypeDescriptionReader)
public:
@@ -55,6 +55,7 @@ private:
QTypeRevision readNumericVersionBinding(QQmlJS::AST::UiScriptBinding *ast);
int readIntBinding(QQmlJS::AST::UiScriptBinding *ast);
QList<QQmlJSScope::Export> readExports(QQmlJS::AST::UiScriptBinding *ast);
+ void readAliases(QQmlJS::AST::UiScriptBinding *ast, const QQmlJSScope::Ptr &scope);
void readInterfaces(QQmlJS::AST::UiScriptBinding *ast, const QQmlJSScope::Ptr &scope);
void checkMetaObjectRevisions(
QQmlJS::AST::UiScriptBinding *ast, QList<QQmlJSScope::Export> *exports);
@@ -75,6 +76,7 @@ private:
QString m_warningMessage;
QList<QQmlJSExportedScope> *m_objects = nullptr;
QStringList *m_dependencies = nullptr;
+ int m_currentCtorIndex = 0;
};
QT_END_NAMESPACE
diff --git a/src/qmlcompiler/qqmljstypepropagator.cpp b/src/qmlcompiler/qqmljstypepropagator.cpp
index 380042e2fe..eb67f69c6b 100644
--- a/src/qmlcompiler/qqmljstypepropagator.cpp
+++ b/src/qmlcompiler/qqmljstypepropagator.cpp
@@ -9,6 +9,8 @@
#include <private/qv4compilerscanfunctions_p.h>
+#include <QtQmlCompiler/private/qqmlsasourcelocation_p.h>
+
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
@@ -26,20 +28,20 @@ using namespace Qt::StringLiterals;
QQmlJSTypePropagator::QQmlJSTypePropagator(const QV4::Compiler::JSUnitGenerator *unitGenerator,
const QQmlJSTypeResolver *typeResolver,
- QQmlJSLogger *logger, QQmlJSTypeInfo *typeInfo,
+ QQmlJSLogger *logger, BasicBlocks basicBlocks,
+ InstructionAnnotations annotations,
QQmlSA::PassManager *passManager)
- : QQmlJSCompilePass(unitGenerator, typeResolver, logger),
- m_typeInfo(typeInfo),
+ : QQmlJSCompilePass(unitGenerator, typeResolver, logger, basicBlocks, annotations),
m_passManager(passManager)
{
}
-QQmlJSCompilePass::InstructionAnnotations QQmlJSTypePropagator::run(
+QQmlJSCompilePass::BlocksAndAnnotations QQmlJSTypePropagator::run(
const Function *function, QQmlJS::DiagnosticMessage *error)
{
m_function = function;
m_error = error;
- m_returnType = m_typeResolver->globalType(m_function->returnType);
+ m_returnType = m_function->returnType;
do {
// Reset the error if we need to do another pass
@@ -48,6 +50,7 @@ QQmlJSCompilePass::InstructionAnnotations QQmlJSTypePropagator::run(
m_prevStateAnnotations = m_state.annotations;
m_state = PassState();
+ m_state.annotations = m_annotations;
m_state.State::operator=(initialState(m_function));
reset();
@@ -58,34 +61,50 @@ QQmlJSCompilePass::InstructionAnnotations QQmlJSTypePropagator::run(
// This means that we won't start over for the same reason again.
} while (m_state.needsMorePasses);
- return m_state.annotations;
+ return { std::move(m_basicBlocks), std::move(m_state.annotations) };
}
-#define INSTR_PROLOGUE_NOT_IMPLEMENTED() \
- setError(u"Instruction \"%1\" not implemented"_s \
- .arg(QString::fromUtf8(__func__))); \
- return;
+#define INSTR_PROLOGUE_NOT_IMPLEMENTED() \
+ setError(u"Instruction \"%1\" not implemented"_s.arg(QString::fromUtf8(__func__))); \
+ return;
#define INSTR_PROLOGUE_NOT_IMPLEMENTED_IGNORE() \
m_logger->log(u"Instruction \"%1\" not implemented"_s.arg(QString::fromUtf8(__func__)), \
qmlCompiler, QQmlJS::SourceLocation()); \
return;
+void QQmlJSTypePropagator::generate_ret_SAcheck()
+{
+ if (!m_function->isProperty)
+ return;
+ QQmlSA::PassManagerPrivate::get(m_passManager)
+ ->analyzeBinding(QQmlJSScope::createQQmlSAElement(
+ m_function->qmlScope.containedType()),
+ QQmlJSScope::createQQmlSAElement(
+ m_state.accumulatorIn().containedType()),
+ QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
+ getCurrentBindingSourceLocation()));
+}
void QQmlJSTypePropagator::generate_Ret()
{
- if (m_passManager != nullptr && m_function->isProperty) {
- m_passManager->analyzeBinding(m_function->qmlScope,
- m_typeResolver->containedType(m_state.accumulatorIn()),
- getCurrentBindingSourceLocation());
- }
+ if (m_passManager != nullptr)
+ generate_ret_SAcheck();
if (m_function->isSignalHandler) {
// Signal handlers cannot return anything.
- } else if (!m_returnType.isValid() && m_state.accumulatorIn().isValid()
- && !m_typeResolver->registerContains(
- m_state.accumulatorIn(), m_typeResolver->voidType())) {
- setError(u"function without type annotation returns %1"_s
- .arg(m_state.accumulatorIn().descriptiveName()));
+ } else if (m_typeResolver->registerContains(
+ m_state.accumulatorIn(), m_typeResolver->voidType())) {
+ // You can always return undefined.
+ } else if (!m_returnType.isValid() && m_state.accumulatorIn().isValid()) {
+ setError(u"function without return type annotation returns %1. This may prevent proper "_s
+ u"compilation to Cpp."_s.arg(m_state.accumulatorIn().descriptiveName()));
+
+ if (m_function->isFullyTyped) {
+ // Do not complain if the function didn't have a valid annotation in the first place.
+ m_logger->log(u"Function without return type annotation returns %1"_s.arg(
+ m_state.accumulatorIn().containedTypeName()),
+ qmlIncompatibleType, getCurrentBindingSourceLocation());
+ }
return;
} else if (!canConvertFromTo(m_state.accumulatorIn(), m_returnType)) {
setError(u"cannot convert from %1 to %2"_s
@@ -93,8 +112,8 @@ void QQmlJSTypePropagator::generate_Ret()
m_returnType.descriptiveName()));
m_logger->log(u"Cannot assign binding of type %1 to %2"_s.arg(
- m_typeResolver->containedTypeName(m_state.accumulatorIn(), true),
- m_typeResolver->containedTypeName(m_returnType, true)),
+ m_state.accumulatorIn().containedTypeName(),
+ m_returnType.containedTypeName()),
qmlIncompatibleType, getCurrentBindingSourceLocation());
return;
}
@@ -124,7 +143,7 @@ void QQmlJSTypePropagator::generate_LoadConst(int index)
void QQmlJSTypePropagator::generate_LoadZero()
{
- setAccumulator(m_typeResolver->globalType(m_typeResolver->intType()));
+ setAccumulator(m_typeResolver->globalType(m_typeResolver->int32Type()));
}
void QQmlJSTypePropagator::generate_LoadTrue()
@@ -149,7 +168,7 @@ void QQmlJSTypePropagator::generate_LoadUndefined()
void QQmlJSTypePropagator::generate_LoadInt(int)
{
- setAccumulator(m_typeResolver->globalType(m_typeResolver->intType()));
+ setAccumulator(m_typeResolver->globalType(m_typeResolver->int32Type()));
}
void QQmlJSTypePropagator::generate_MoveConst(int constIndex, int destTemp)
@@ -278,38 +297,36 @@ void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name, bool isM
{
auto location = getCurrentSourceLocation();
- if (m_function->qmlScope->isInCustomParserParent()) {
+ const auto qmlScopeContained = m_function->qmlScope.containedType();
+ if (qmlScopeContained->isInCustomParserParent()) {
// Only ignore custom parser based elements if it's not Connections.
- if (m_function->qmlScope->baseType().isNull()
- || m_function->qmlScope->baseType()->internalName() != u"QQmlConnections"_s)
+ if (qmlScopeContained->baseType().isNull()
+ || qmlScopeContained->baseType()->internalName() != u"QQmlConnections"_s)
return;
}
if (isMethod) {
- if (isCallingProperty(m_function->qmlScope, name))
+ if (isCallingProperty(qmlScopeContained, name))
return;
- } else if (propertyResolution(m_function->qmlScope, name) != PropertyMissing) {
+ } else if (propertyResolution(qmlScopeContained, name) != PropertyMissing) {
return;
}
- std::optional<FixSuggestion> suggestion;
+ std::optional<QQmlJSFixSuggestion> suggestion;
- auto childScopes = m_function->qmlScope->childScopes();
- for (qsizetype i = 0; i < m_function->qmlScope->childScopes().size(); i++) {
+ const auto childScopes = m_function->qmlScope.containedType()->childScopes();
+ for (qsizetype i = 0, end = childScopes.size(); i < end; i++) {
auto &scope = childScopes[i];
if (location.offset > scope->sourceLocation().offset) {
- if (i + 1 < childScopes.size()
+ if (i + 1 < end
&& childScopes.at(i + 1)->sourceLocation().offset < location.offset)
continue;
if (scope->childScopes().size() == 0)
continue;
- const auto jsId = scope->childScopes().first()->findJSIdentifier(name);
+ const auto jsId = scope->childScopes().first()->jsIdentifier(name);
if (jsId.has_value() && jsId->kind == QQmlJSScope::JavaScriptIdentifier::Injected) {
-
- suggestion = FixSuggestion {};
-
const QQmlJSScope::JavaScriptIdentifier id = jsId.value();
QQmlJS::SourceLocation fixLocation = id.location;
@@ -328,15 +345,15 @@ void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name, bool isM
fixString += handler.isMultiline ? u") "_s : u") => "_s;
- suggestion->fixes << FixSuggestion::Fix {
- name
- + QString::fromLatin1(" is accessible in this scope because "
- "you are handling a signal at %1:%2. Use a "
- "function instead.\n")
- .arg(id.location.startLine)
- .arg(id.location.startColumn),
- fixLocation, fixString, QString(), false
+ suggestion = QQmlJSFixSuggestion {
+ name + u" is accessible in this scope because you are handling a signal"
+ " at %1:%2. Use a function instead.\n"_s
+ .arg(id.location.startLine)
+ .arg(id.location.startColumn),
+ fixLocation,
+ fixString
};
+ suggestion->setAutoApplicable();
}
break;
}
@@ -346,19 +363,19 @@ void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name, bool isM
// This heuristic does not recognize all instances of this occurring but should be sufficient
// protection against wrongly suggesting to add an id to the view to access the model that way
// which is very misleading
+ const auto qmlScope = m_function->qmlScope.containedType();
if (name == u"model" || name == u"index") {
- if (QQmlJSScope::ConstPtr parent = m_function->qmlScope->parentScope(); !parent.isNull()) {
+ if (const QQmlJSScope::ConstPtr parent = qmlScope->parentScope(); !parent.isNull()) {
const auto bindings = parent->ownPropertyBindings(u"delegate"_s);
for (auto it = bindings.first; it != bindings.second; it++) {
if (!it->hasObject())
continue;
- if (it->objectType() == m_function->qmlScope) {
- suggestion = FixSuggestion {};
-
- suggestion->fixes << FixSuggestion::Fix {
- name + u" is implicitly injected into this delegate. Add a required property instead."_s,
- m_function->qmlScope->sourceLocation(), QString(), QString(), true
+ if (it->objectType() == qmlScope) {
+ suggestion = QQmlJSFixSuggestion {
+ name + " is implicitly injected into this delegate."
+ " Add a required property instead."_L1,
+ qmlScope->sourceLocation()
};
};
@@ -368,49 +385,45 @@ void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name, bool isM
}
if (!suggestion.has_value()) {
- for (QQmlJSScope::ConstPtr scope = m_function->qmlScope; !scope.isNull();
- scope = scope->parentScope()) {
+ for (QQmlJSScope::ConstPtr scope = qmlScope; !scope.isNull(); scope = scope->parentScope()) {
if (scope->hasProperty(name)) {
- const QString id = m_function->addressableScopes.id(scope);
-
- suggestion = FixSuggestion {};
+ const QString id = m_function->addressableScopes.id(scope, qmlScope);
QQmlJS::SourceLocation fixLocation = location;
fixLocation.length = 0;
- suggestion->fixes << FixSuggestion::Fix {
- name + QLatin1String(" is a member of a parent element\n")
- + QLatin1String(" You can qualify the access with its id "
- "to avoid this warning:\n"),
- fixLocation, (id.isEmpty() ? u"<id>."_s : (id + u'.')), QString(), id.isEmpty()
+ suggestion = QQmlJSFixSuggestion{
+ name
+ + " is a member of a parent element.\n You can qualify the access "
+ "with its id to avoid this warning.\n"_L1,
+ fixLocation, (id.isEmpty() ? u"<id>."_s : (id + u'.'))
};
- if (id.isEmpty()) {
- suggestion->fixes << FixSuggestion::Fix {
- u"You first have to give the element an id"_s, QQmlJS::SourceLocation {}, {}
- };
- }
+ if (id.isEmpty())
+ suggestion->setHint("You first have to give the element an id"_L1);
+ else
+ suggestion->setAutoApplicable();
}
}
}
if (!suggestion.has_value() && !m_function->addressableScopes.componentsAreBound()
&& m_function->addressableScopes.existsAnywhereInDocument(name)) {
- FixSuggestion::Fix bindComponents;
const QLatin1String replacement = "pragma ComponentBehavior: Bound"_L1;
- bindComponents.replacementString = replacement + '\n'_L1;
- bindComponents.message = "Set \"%1\" in order to use IDs "
- "from outer components in nested components."_L1.arg(replacement);
- bindComponents.cutLocation = QQmlJS::SourceLocation(0, 0, 1, 1);
- bindComponents.isHint = false;
- suggestion = FixSuggestion {{ bindComponents }};
+ QQmlJSFixSuggestion bindComponents {
+ "Set \"%1\" in order to use IDs from outer components in nested components."_L1
+ .arg(replacement),
+ QQmlJS::SourceLocation(0, 0, 1, 1),
+ replacement + '\n'_L1
+ };
+ bindComponents.setAutoApplicable();
+ suggestion = bindComponents;
}
if (!suggestion.has_value()) {
if (auto didYouMean =
- QQmlJSUtils::didYouMean(name,
- m_function->qmlScope->properties().keys()
- + m_function->qmlScope->methods().keys(),
- location);
+ QQmlJSUtils::didYouMean(
+ name, qmlScope->properties().keys() + qmlScope->methods().keys(),
+ location);
didYouMean.has_value()) {
suggestion = didYouMean;
}
@@ -468,52 +481,6 @@ void QQmlJSTypePropagator::checkDeprecated(QQmlJSScope::ConstPtr scope, const QS
m_logger->log(message, qmlDeprecated, getCurrentSourceLocation());
}
-bool QQmlJSTypePropagator::isRestricted(const QString &propertyName) const
-{
- QString restrictedKind;
-
- const auto accumulatorIn = m_state.registers.find(Accumulator);
- if (accumulatorIn == m_state.registers.end())
- return false;
-
- if (accumulatorIn.value().content.isList() && propertyName != u"length") {
- restrictedKind = u"a list"_s;
- } else if (accumulatorIn.value().content.isEnumeration()) {
- const auto metaEn = accumulatorIn.value().content.enumeration();
- if (metaEn.isScoped()) {
- if (!metaEn.hasKey(propertyName))
- restrictedKind = u"an enum"_s;
- } else {
- restrictedKind = u"an unscoped enum"_s;
- }
- } else if (accumulatorIn.value().content.isMethod()) {
- auto overloadSet = accumulatorIn.value().content.method();
- auto potentiallyJSMethod = std::any_of(
- overloadSet.cbegin(), overloadSet.cend(),
- [](const QQmlJSMetaMethod &overload){
- return overload.isJavaScriptFunction();
- });
- if (potentiallyJSMethod) {
- /* JS global constructors like Number get detected as methods
- However, they still have properties that can be accessed
- e.g. Number.EPSILON. This also isn't restricted to constructor
- functions, so use isJavaScriptFunction as an overapproximation.
- That catches also QQmlV4Function, but we're purging uses of it
- anyway.
- */
- return false;
- }
- restrictedKind = u"a method"_s;
- }
-
- if (!restrictedKind.isEmpty())
- m_logger->log(u"Type is %1. You cannot access \"%2\" from here."_s.arg(restrictedKind,
- propertyName),
- qmlRestrictedType, getCurrentSourceLocation());
-
- return !restrictedKind.isEmpty();
-}
-
// Only to be called once a lookup has already failed
QQmlJSTypePropagator::PropertyResolution QQmlJSTypePropagator::propertyResolution(
QQmlJSScope::ConstPtr scope, const QString &propertyName) const
@@ -554,15 +521,17 @@ bool QQmlJSTypePropagator::isCallingProperty(QQmlJSScope::ConstPtr scope, const
if (!methods.isEmpty()) {
errorType = u"shadowed by a property."_s;
switch (methods.first().methodType()) {
- case QQmlJSMetaMethod::Signal:
+ case QQmlJSMetaMethodType::Signal:
propertyType = u"Signal"_s;
break;
- case QQmlJSMetaMethod::Slot:
+ case QQmlJSMetaMethodType::Slot:
propertyType = u"Slot"_s;
break;
- case QQmlJSMetaMethod::Method:
+ case QQmlJSMetaMethodType::Method:
propertyType = u"Method"_s;
break;
+ default:
+ Q_UNREACHABLE();
}
} else if (m_typeResolver->equals(property.type(), m_typeResolver->varType())) {
errorType =
@@ -580,6 +549,18 @@ bool QQmlJSTypePropagator::isCallingProperty(QQmlJSScope::ConstPtr scope, const
return true;
}
+
+void QQmlJSTypePropagator::generate_LoadQmlContextPropertyLookup_SAcheck(const QString &name)
+{
+ const auto qmlScope = m_function->qmlScope.containedType();
+ QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeRead(
+ QQmlJSScope::createQQmlSAElement(qmlScope), name,
+ QQmlJSScope::createQQmlSAElement(qmlScope),
+ QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
+ getCurrentBindingSourceLocation()));
+}
+
+
void QQmlJSTypePropagator::generate_LoadQmlContextPropertyLookup(int index)
{
// LoadQmlContextPropertyLookup does not use accumulatorIn. It always refers to the scope.
@@ -588,51 +569,94 @@ void QQmlJSTypePropagator::generate_LoadQmlContextPropertyLookup(int index)
const int nameIndex = m_jsUnitGenerator->lookupNameIndex(index);
const QString name = m_jsUnitGenerator->stringForIndex(nameIndex);
- setAccumulator(m_typeResolver->scopedType(m_function->qmlScope, name));
+ setAccumulator(m_typeResolver->scopedType(m_function->qmlScope, name, index));
+ const auto qmlScope = m_function->qmlScope.containedType();
if (!m_state.accumulatorOut().isValid() && m_typeResolver->isPrefix(name)) {
- const QQmlJSRegisterContent inType = m_typeResolver->globalType(m_function->qmlScope);
+ const QQmlJSRegisterContent inType = m_typeResolver->globalType(qmlScope);
setAccumulator(QQmlJSRegisterContent::create(
- m_typeResolver->voidType(), nameIndex, QQmlJSRegisterContent::ScopeModulePrefix,
- m_typeResolver->containedType(inType)));
+ nameIndex, m_typeResolver->voidType(), QQmlJSRegisterContent::ScopeModulePrefix,
+ inType.containedType()));
return;
}
- checkDeprecated(m_function->qmlScope, name, false);
+ checkDeprecated(qmlScope, name, false);
if (!m_state.accumulatorOut().isValid()) {
setError(u"Cannot access value for name "_s + name);
handleUnqualifiedAccess(name, false);
- } else if (m_typeResolver->genericType(m_state.accumulatorOut().storedType()).isNull()) {
+ return;
+ }
+
+ const QQmlJSScope::ConstPtr retrieved
+ = m_typeResolver->genericType(m_state.accumulatorOut().containedType());
+
+ if (retrieved.isNull()) {
// It should really be valid.
// We get the generic type from aotContext->loadQmlContextPropertyIdLookup().
setError(u"Cannot determine generic type for "_s + name);
- } else if (m_passManager != nullptr) {
- m_passManager->analyzeRead(m_function->qmlScope, name, m_function->qmlScope,
- getCurrentSourceLocation());
+ return;
+ }
+
+ if (m_state.accumulatorOut().variant() == QQmlJSRegisterContent::ObjectById
+ && !retrieved->isReferenceType()) {
+ setError(u"Cannot retrieve a non-object type by ID: "_s + name);
+ return;
}
+ if (m_passManager != nullptr)
+ generate_LoadQmlContextPropertyLookup_SAcheck(name);
+
if (m_state.accumulatorOut().variant() == QQmlJSRegisterContent::ScopeAttached)
m_attachedContext = QQmlJSScope::ConstPtr();
}
-void QQmlJSTypePropagator::generate_StoreNameSloppy(int nameIndex)
+void QQmlJSTypePropagator::generate_StoreNameCommon_SAcheck(const QQmlJSRegisterContent &in, const QString &name)
+{
+ const auto qmlScope = m_function->qmlScope.containedType();
+ QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeWrite(
+ QQmlJSScope::createQQmlSAElement(qmlScope), name,
+ QQmlJSScope::createQQmlSAElement(in.containedType()),
+ QQmlJSScope::createQQmlSAElement(qmlScope),
+ QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
+ getCurrentBindingSourceLocation()));
+}
+
+/*!
+ \internal
+ As far as type propagation is involved, StoreNameSloppy and
+ StoreNameStrict are completely the same
+ StoreNameStrict is rejecting a few writes (where the variable was not
+ defined before) that would work in a sloppy context in JS, but the
+ compiler would always reject this. And for type propagation, this does
+ not matter at all.
+ \a nameIndex is the index in the string table corresponding to
+ the name which we are storing
+ */
+void QQmlJSTypePropagator::generate_StoreNameCommon(int nameIndex)
{
const QString name = m_jsUnitGenerator->stringForIndex(nameIndex);
const QQmlJSRegisterContent type = m_typeResolver->scopedType(m_function->qmlScope, name);
const QQmlJSRegisterContent in = m_state.accumulatorIn();
if (!type.isValid()) {
+ handleUnqualifiedAccess(name, false);
setError(u"Cannot find name "_s + name);
return;
}
if (!type.isProperty()) {
+ QString message = type.isMethod() ? u"Cannot assign to method %1"_s
+ : u"Cannot assign to non-property %1"_s;
+ // The interpreter treats methods as read-only properties in its error messages
+ // and we lack a better fitting category. We might want to revisit this later.
+ m_logger->log(message.arg(name), qmlReadOnlyProperty,
+ getCurrentSourceLocation());
setError(u"Cannot assign to non-property "_s + name);
return;
}
- if (!type.isWritable() && !m_function->qmlScope->hasOwnProperty(name)) {
+ if (!type.isWritable()) {
setError(u"Can't assign to read-only property %1"_s.arg(name));
m_logger->log(u"Cannot assign to read-only property %1"_s.arg(name), qmlReadOnlyProperty,
@@ -646,53 +670,95 @@ void QQmlJSTypePropagator::generate_StoreNameSloppy(int nameIndex)
.arg(in.descriptiveName(), type.descriptiveName()));
}
- if (m_passManager != nullptr) {
- m_passManager->analyzeWrite(m_function->qmlScope, name,
- m_typeResolver->containedType(in),
- m_function->qmlScope, getCurrentSourceLocation());
- }
+ if (m_passManager != nullptr)
+ generate_StoreNameCommon_SAcheck(in, name);
- m_state.setHasSideEffects(true);
if (m_typeResolver->canHoldUndefined(in) && !m_typeResolver->canHoldUndefined(type)) {
- if (type.property().reset().isEmpty())
- setError(u"Cannot assign potential undefined to %1"_s.arg(type.descriptiveName()));
- else if (m_typeResolver->registerIsStoredIn(in, m_typeResolver->voidType()))
+ if (m_typeResolver->registerContains(in, m_typeResolver->voidType()))
addReadAccumulator(m_typeResolver->globalType(m_typeResolver->varType()));
else
addReadAccumulator(in);
} else {
addReadAccumulator(type);
}
+
+ m_state.setHasSideEffects(true);
+}
+
+void QQmlJSTypePropagator::generate_StoreNameSloppy(int nameIndex)
+{
+ return generate_StoreNameCommon(nameIndex);
}
void QQmlJSTypePropagator::generate_StoreNameStrict(int name)
{
- m_state.setHasSideEffects(true);
- Q_UNUSED(name)
- INSTR_PROLOGUE_NOT_IMPLEMENTED();
+ return generate_StoreNameCommon(name);
}
-void QQmlJSTypePropagator::generate_LoadElement(int base)
+bool QQmlJSTypePropagator::checkForEnumProblems(
+ const QQmlJSRegisterContent &base, const QString &propertyName)
{
- const QQmlJSRegisterContent baseRegister = m_state.registers[base].content;
+ if (base.isEnumeration()) {
+ const auto metaEn = base.enumeration();
+ if (!metaEn.hasKey(propertyName)) {
+ auto fixSuggestion = QQmlJSUtils::didYouMean(propertyName, metaEn.keys(),
+ getCurrentSourceLocation());
+ const QString error = u"\"%1\" is not an entry of enum \"%2\"."_s
+ .arg(propertyName, metaEn.name());
+ setError(error);
+ m_logger->log(
+ error, qmlMissingEnumEntry, getCurrentSourceLocation(), true, true,
+ fixSuggestion);
+ return true;
+ }
+ } else if (base.variant() == QQmlJSRegisterContent::MetaType) {
+ const QQmlJSMetaEnum metaEn = base.scopeType()->enumeration(propertyName);
+ if (metaEn.isValid() && !metaEn.isScoped() && !metaEn.isQml()) {
+ const QString error
+ = u"You cannot access unscoped enum \"%1\" from here."_s.arg(propertyName);
+ setError(error);
+ m_logger->log(error, qmlRestrictedType, getCurrentSourceLocation());
+ return true;
+ }
+ }
- if ((baseRegister.storedType()->accessSemantics() != QQmlJSScope::AccessSemantics::Sequence
- && !m_typeResolver->registerIsStoredIn(baseRegister, m_typeResolver->stringType()))
- || !m_typeResolver->isNumeric(m_state.accumulatorIn())) {
+ return false;
+}
+
+void QQmlJSTypePropagator::generate_LoadElement(int base)
+{
+ const auto fallback = [&]() {
const auto jsValue = m_typeResolver->globalType(m_typeResolver->jsValueType());
addReadAccumulator(jsValue);
addReadRegister(base, jsValue);
setAccumulator(jsValue);
+ };
+
+ const QQmlJSRegisterContent baseRegister = m_state.registers[base].content;
+ if (!baseRegister.isList()
+ && !m_typeResolver->registerContains(baseRegister, m_typeResolver->stringType())) {
+ fallback();
return;
}
+ addReadRegister(base, baseRegister);
- if (m_typeResolver->isIntegral(m_state.accumulatorIn()))
- addReadAccumulator(m_typeResolver->globalType(m_typeResolver->intType()));
- else
- addReadAccumulator(m_typeResolver->globalType(m_typeResolver->realType()));
+ if (m_typeResolver->isNumeric(m_state.accumulatorIn())) {
+ const auto contained = m_state.accumulatorIn().containedType();
+ if (m_typeResolver->isSignedInteger(contained))
+ addReadAccumulator(m_typeResolver->globalType(m_typeResolver->sizeType()));
+ else if (m_typeResolver->isUnsignedInteger(contained))
+ addReadAccumulator(m_typeResolver->globalType(m_typeResolver->uint32Type()));
+ else
+ addReadAccumulator(m_typeResolver->globalType(m_typeResolver->realType()));
+ } else if (m_typeResolver->isNumeric(m_typeResolver->extractNonVoidFromOptionalType(
+ m_state.accumulatorIn()))) {
+ addReadAccumulator(m_state.accumulatorIn());
+ } else {
+ fallback();
+ return;
+ }
- addReadRegister(base, baseRegister);
// We can end up with undefined.
setAccumulator(m_typeResolver->merge(
m_typeResolver->valueType(baseRegister),
@@ -704,7 +770,7 @@ void QQmlJSTypePropagator::generate_StoreElement(int base, int index)
const QQmlJSRegisterContent baseRegister = m_state.registers[base].content;
const QQmlJSRegisterContent indexRegister = checkedInputRegister(index);
- if (baseRegister.storedType()->accessSemantics() != QQmlJSScope::AccessSemantics::Sequence
+ if (!baseRegister.isList()
|| !m_typeResolver->isNumeric(indexRegister)) {
const auto jsValue = m_typeResolver->globalType(m_typeResolver->jsValueType());
addReadAccumulator(jsValue);
@@ -717,8 +783,11 @@ void QQmlJSTypePropagator::generate_StoreElement(int base, int index)
return;
}
- if (m_typeResolver->isIntegral(indexRegister))
- addReadRegister(index, m_typeResolver->globalType(m_typeResolver->intType()));
+ const auto contained = indexRegister.containedType();
+ if (m_typeResolver->isSignedInteger(contained))
+ addReadRegister(index, m_typeResolver->globalType(m_typeResolver->int32Type()));
+ else if (m_typeResolver->isUnsignedInteger(contained))
+ addReadRegister(index, m_typeResolver->globalType(m_typeResolver->uint32Type()));
else
addReadRegister(index, m_typeResolver->globalType(m_typeResolver->realType()));
@@ -733,7 +802,23 @@ void QQmlJSTypePropagator::generate_StoreElement(int base, int index)
m_state.setHasSideEffects(true);
}
-void QQmlJSTypePropagator::propagatePropertyLookup(const QString &propertyName)
+void QQmlJSTypePropagator::propagatePropertyLookup_SAcheck(const QString &propertyName)
+{
+ const bool isAttached =
+ m_state.accumulatorIn().variant() == QQmlJSRegisterContent::ObjectAttached;
+
+ QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeRead(
+ QQmlJSScope::createQQmlSAElement(
+ m_state.accumulatorIn().containedType()),
+ propertyName,
+ QQmlJSScope::createQQmlSAElement(isAttached
+ ? m_attachedContext
+ : m_function->qmlScope.containedType()),
+ QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
+ getCurrentBindingSourceLocation()));
+}
+
+void QQmlJSTypePropagator::propagatePropertyLookup(const QString &propertyName, int lookupIndex)
{
setAccumulator(
m_typeResolver->memberType(
@@ -741,62 +826,17 @@ void QQmlJSTypePropagator::propagatePropertyLookup(const QString &propertyName)
m_state.accumulatorIn().isImportNamespace()
? m_jsUnitGenerator->stringForIndex(m_state.accumulatorIn().importNamespace())
+ u'.' + propertyName
- : propertyName));
-
- if (m_typeInfo != nullptr
- && m_state.accumulatorIn().variant() == QQmlJSRegisterContent::ScopeAttached) {
- QQmlJSScope::ConstPtr attachedType = m_typeResolver->originalType(
- m_state.accumulatorIn().scopeType());
-
- for (QQmlJSScope::ConstPtr scope = m_function->qmlScope->parentScope(); !scope.isNull();
- scope = scope->parentScope()) {
- if (m_typeInfo->usedAttachedTypes.values(scope).contains(attachedType)) {
-
- // Ignore enum accesses, as these will not cause the attached object to be created
- if (m_state.accumulatorOut().isValid() && m_state.accumulatorOut().isEnumeration())
- continue;
-
- const QString id = m_function->addressableScopes.id(scope);
-
- FixSuggestion suggestion;
-
- QQmlJS::SourceLocation fixLocation = getCurrentSourceLocation();
- fixLocation.length = 0;
-
- suggestion.fixes << FixSuggestion::Fix { u"Reference it by id instead:"_s,
- fixLocation,
- id.isEmpty() ? u"<id>."_s : (id + u'.'),
- QString(), id.isEmpty() };
-
- fixLocation = scope->sourceLocation();
- fixLocation.length = 0;
-
- if (id.isEmpty()) {
- suggestion.fixes
- << FixSuggestion::Fix { u"You first have to give the element an id"_s,
- QQmlJS::SourceLocation {},
- {} };
- }
-
- m_logger->log(
- u"Using attached type %1 already initialized in a parent scope."_s.arg(
- m_state.accumulatorIn().scopeType()->internalName()),
- qmlAttachedPropertyReuse, getCurrentSourceLocation(), true, true,
- suggestion);
- }
- }
- m_typeInfo->usedAttachedTypes.insert(m_function->qmlScope, attachedType);
- }
+ : propertyName, lookupIndex));
if (!m_state.accumulatorOut().isValid()) {
if (m_typeResolver->isPrefix(propertyName)) {
Q_ASSERT(m_state.accumulatorIn().isValid());
addReadAccumulator(m_state.accumulatorIn());
setAccumulator(QQmlJSRegisterContent::create(
- m_state.accumulatorIn().storedType(),
m_jsUnitGenerator->getStringId(propertyName),
+ m_state.accumulatorIn().containedType(),
QQmlJSRegisterContent::ObjectModulePrefix,
- m_typeResolver->containedType(m_state.accumulatorIn())));
+ m_state.accumulatorIn().containedType()));
return;
}
if (m_state.accumulatorIn().isImportNamespace())
@@ -808,31 +848,46 @@ void QQmlJSTypePropagator::propagatePropertyLookup(const QString &propertyName)
u"Cannot access singleton as a property of an object. Did you want to access an attached object?"_s,
qmlAccessSingleton, getCurrentSourceLocation());
setAccumulator(QQmlJSRegisterContent());
+ } else if (m_state.accumulatorOut().isEnumeration()) {
+ switch (m_state.accumulatorIn().variant()) {
+ case QQmlJSRegisterContent::ExtensionObjectEnum:
+ case QQmlJSRegisterContent::MetaType:
+ case QQmlJSRegisterContent::ObjectAttached:
+ case QQmlJSRegisterContent::ObjectEnum:
+ case QQmlJSRegisterContent::ObjectModulePrefix:
+ case QQmlJSRegisterContent::ScopeAttached:
+ case QQmlJSRegisterContent::ScopeModulePrefix:
+ case QQmlJSRegisterContent::Singleton:
+ break; // OK, can look up enums on that thing
+ default:
+ setAccumulator(QQmlJSRegisterContent());
+ }
}
- const bool isRestrictedProperty = isRestricted(propertyName);
-
if (!m_state.accumulatorOut().isValid()) {
+ if (checkForEnumProblems(m_state.accumulatorIn(), propertyName))
+ return;
+
setError(u"Cannot load property %1 from %2."_s
.arg(propertyName, m_state.accumulatorIn().descriptiveName()));
- if (isRestrictedProperty)
- return;
-
- const QString typeName = m_typeResolver->containedTypeName(m_state.accumulatorIn(), true);
+ const QString typeName = m_state.accumulatorIn().containedTypeName();
if (typeName == u"QVariant")
return;
if (m_state.accumulatorIn().isList() && propertyName == u"length")
return;
- auto baseType = m_typeResolver->containedType(m_state.accumulatorIn());
+ auto baseType = m_state.accumulatorIn().containedType();
// Warn separately when a property is only not found because of a missing type
if (propertyResolution(baseType, propertyName) != PropertyMissing)
return;
- std::optional<FixSuggestion> fixSuggestion;
+ if (baseType->isScript())
+ return;
+
+ std::optional<QQmlJSFixSuggestion> fixSuggestion;
if (auto suggestion = QQmlJSUtils::didYouMean(propertyName, baseType->properties().keys(),
getCurrentSourceLocation());
@@ -854,7 +909,7 @@ void QQmlJSTypePropagator::propagatePropertyLookup(const QString &propertyName)
}
}
- m_logger->log(u"Property \"%1\" not found on type \"%2\""_s.arg(propertyName).arg(typeName),
+ m_logger->log(u"Member \"%1\" not found on type \"%2\""_s.arg(propertyName).arg(typeName),
qmlMissingProperty, getCurrentSourceLocation(), true, true, fixSuggestion);
return;
}
@@ -865,6 +920,22 @@ void QQmlJSTypePropagator::propagatePropertyLookup(const QString &propertyName)
}
if (m_state.accumulatorOut().isProperty()) {
+ const QQmlJSScope::ConstPtr mathObject
+ = m_typeResolver->jsGlobalObject()->property(u"Math"_s).type();
+ if (m_typeResolver->registerContains(m_state.accumulatorIn(), mathObject)) {
+ QQmlJSMetaProperty prop;
+ prop.setPropertyName(propertyName);
+ prop.setTypeName(u"double"_s);
+ prop.setType(m_typeResolver->realType());
+ setAccumulator(
+ QQmlJSRegisterContent::create(
+ prop, m_state.accumulatorIn().resultLookupIndex(), lookupIndex,
+ QQmlJSRegisterContent::GenericObjectProperty, mathObject)
+ );
+
+ return;
+ }
+
if (m_typeResolver->registerContains(
m_state.accumulatorOut(), m_typeResolver->voidType())) {
setError(u"Type %1 does not have a property %2 for reading"_s
@@ -879,17 +950,11 @@ void QQmlJSTypePropagator::propagatePropertyLookup(const QString &propertyName)
}
}
- if (m_passManager != nullptr) {
- const bool isAttached =
- m_state.accumulatorIn().variant() == QQmlJSRegisterContent::ObjectAttached;
-
- m_passManager->analyzeRead(
- m_typeResolver->containedType(m_state.accumulatorIn()), propertyName,
- isAttached ? m_attachedContext : m_function->qmlScope, getCurrentSourceLocation());
- }
+ if (m_passManager != nullptr)
+ propagatePropertyLookup_SAcheck(propertyName);
if (m_state.accumulatorOut().variant() == QQmlJSRegisterContent::ObjectAttached)
- m_attachedContext = m_typeResolver->containedType(m_state.accumulatorIn());
+ m_attachedContext = m_state.accumulatorIn().containedType();
switch (m_state.accumulatorOut().variant()) {
case QQmlJSRegisterContent::ObjectEnum:
@@ -920,14 +985,30 @@ void QQmlJSTypePropagator::generate_LoadOptionalProperty(int name, int offset)
void QQmlJSTypePropagator::generate_GetLookup(int index)
{
- propagatePropertyLookup(m_jsUnitGenerator->lookupName(index));
+ propagatePropertyLookup(m_jsUnitGenerator->lookupName(index), index);
}
void QQmlJSTypePropagator::generate_GetOptionalLookup(int index, int offset)
{
- Q_UNUSED(index);
Q_UNUSED(offset);
- INSTR_PROLOGUE_NOT_IMPLEMENTED();
+ saveRegisterStateForJump(offset);
+ propagatePropertyLookup(m_jsUnitGenerator->lookupName(index), index);
+}
+
+void QQmlJSTypePropagator::generate_StoreProperty_SAcheck(const QString propertyName, const QQmlJSRegisterContent &callBase)
+{
+ const bool isAttached = callBase.variant() == QQmlJSRegisterContent::ObjectAttached;
+
+ QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeWrite(
+ QQmlJSScope::createQQmlSAElement(callBase.containedType()),
+ propertyName,
+ QQmlJSScope::createQQmlSAElement(
+ m_state.accumulatorIn().containedType()),
+ QQmlJSScope::createQQmlSAElement(isAttached
+ ? m_attachedContext
+ : m_function->qmlScope.containedType()),
+ QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
+ getCurrentBindingSourceLocation()));
}
void QQmlJSTypePropagator::generate_StoreProperty(int nameIndex, int base)
@@ -942,7 +1023,13 @@ void QQmlJSTypePropagator::generate_StoreProperty(int nameIndex, int base)
return;
}
- if (!property.isWritable()) {
+ if (property.containedType().isNull()) {
+ setError(u"Cannot determine type for property %1 of type %2"_s.arg(
+ propertyName, callBase.descriptiveName()));
+ return;
+ }
+
+ if (!property.isWritable() && !property.containedType()->isListProperty()) {
setError(u"Can't assign to read-only property %1"_s.arg(propertyName));
m_logger->log(u"Cannot assign to read-only property %1"_s.arg(propertyName),
@@ -957,18 +1044,25 @@ void QQmlJSTypePropagator::generate_StoreProperty(int nameIndex, int base)
return;
}
- if (m_passManager != nullptr) {
- const bool isAttached = callBase.variant() == QQmlJSRegisterContent::ObjectAttached;
+ if (m_passManager != nullptr)
+ generate_StoreProperty_SAcheck(propertyName, callBase);
- m_passManager->analyzeWrite(m_typeResolver->containedType(callBase), propertyName,
- m_typeResolver->containedType(m_state.accumulatorIn()),
- isAttached ? m_attachedContext : m_function->qmlScope,
- getCurrentSourceLocation());
- }
+ // If the input can hold undefined we must not coerce it to the property type
+ // as that might eliminate an undefined value. For example, undefined -> string
+ // becomes "undefined".
+ // We need the undefined value for either resetting the property if that is supported
+ // or generating an exception otherwise. Therefore we explicitly require the value to
+ // be given as QVariant. This triggers the QVariant fallback path that's also used for
+ // shadowable properties. QVariant can hold undefined and the lookup functions will
+ // handle that appropriately.
- m_state.setHasSideEffects(true);
- addReadAccumulator(property);
+ const QQmlJSScope::ConstPtr varType = m_typeResolver->varType();
+ const QQmlJSRegisterContent readType = m_typeResolver->canHoldUndefined(m_state.accumulatorIn())
+ ? property.castTo(varType)
+ : std::move(property);
+ addReadAccumulator(readType);
addReadRegister(base, callBase);
+ m_state.setHasSideEffects(true);
}
void QQmlJSTypePropagator::generate_SetLookup(int index, int base)
@@ -1028,88 +1122,123 @@ static bool isLoggingMethod(const QString &consoleMethod)
|| consoleMethod == u"warn" || consoleMethod == u"error";
}
-void QQmlJSTypePropagator::generate_CallProperty(int nameIndex, int base, int argc, int argv)
+void QQmlJSTypePropagator::generate_CallProperty_SCMath(int base, int argc, int argv)
{
- Q_ASSERT(m_state.registers.contains(base));
- const auto callBase = m_state.registers[base].content;
- const QString propertyName = m_jsUnitGenerator->stringForIndex(nameIndex);
+ // If we call a method on the Math object we don't need the actual Math object. We do need
+ // to transfer the type information to the code generator so that it knows that this is the
+ // Math object. Read the base register as void. void isn't stored, and the place where it's
+ // created will be optimized out if there are no other readers. The code generator can
+ // retrieve the original type and determine that it was the Math object.
- if (m_typeResolver->registerContains(
- callBase, m_typeResolver->jsGlobalObject()->property(u"Math"_s).type())) {
+ addReadRegister(base, m_typeResolver->globalType(m_typeResolver->voidType()));
- // If we call a method on the Math object we don't need the actual Math object. We do need
- // to transfer the type information to the code generator so that it knows that this is the
- // Math object. Read the base register as void. void isn't stored, and the place where it's
- // created will be optimized out if there are no other readers. The code generator can
- // retrieve the original type and determine that it was the Math object.
- addReadRegister(base, m_typeResolver->globalType(m_typeResolver->voidType()));
+ QQmlJSRegisterContent realType = m_typeResolver->returnType(
+ m_typeResolver->realType(), QQmlJSRegisterContent::MethodReturnValue,
+ m_typeResolver->mathObject());
+ for (int i = 0; i < argc; ++i)
+ addReadRegister(argv + i, realType);
+ setAccumulator(realType);
+}
- QQmlJSRegisterContent realType = m_typeResolver->globalType(m_typeResolver->realType());
- for (int i = 0; i < argc; ++i)
- addReadRegister(argv + i, realType);
- setAccumulator(realType);
- return;
- }
+void QQmlJSTypePropagator::generate_CallProperty_SCconsole(int base, int argc, int argv)
+{
+ const QQmlJSRegisterContent voidType
+ = m_typeResolver->globalType(m_typeResolver->voidType());
- if (m_typeResolver->registerContains(
- callBase, m_typeResolver->jsGlobalObject()->property(u"console"_s).type())
- && isLoggingMethod(propertyName)) {
+ // If we call a method on the console object we don't need the console object.
+ addReadRegister(base, voidType);
- const QQmlJSRegisterContent voidType
- = m_typeResolver->globalType(m_typeResolver->voidType());
+ const QQmlJSRegisterContent stringType
+ = m_typeResolver->globalType(m_typeResolver->stringType());
- // If we call a method on the console object we don't need the console object.
- addReadRegister(base, voidType);
+ if (argc > 0) {
+ const QQmlJSRegisterContent firstContent = m_state.registers[argv].content;
+ const QQmlJSScope::ConstPtr firstArg = firstContent.containedType();
+ switch (firstArg->accessSemantics()) {
+ case QQmlJSScope::AccessSemantics::Reference:
+ // We cannot know whether this will be a logging category at run time.
+ // Therefore we always pass any object types as special last argument.
+ addReadRegister(argv, m_typeResolver->globalType(
+ m_typeResolver->genericType(firstArg)));
+ break;
+ case QQmlJSScope::AccessSemantics::Sequence:
+ addReadRegister(argv, firstContent);
+ break;
+ default:
+ addReadRegister(argv, stringType);
+ break;
+ }
+ }
- const QQmlJSRegisterContent stringType
- = m_typeResolver->globalType(m_typeResolver->stringType());
+ for (int i = 1; i < argc; ++i) {
+ const QQmlJSRegisterContent argContent = m_state.registers[argv + i].content;
+ const QQmlJSScope::ConstPtr arg = argContent.containedType();
+ addReadRegister(
+ argv + i,
+ arg->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence
+ ? argContent
+ : stringType);
+ }
- if (argc > 0) {
- const QQmlJSScope::ConstPtr firstArg
- = m_typeResolver->containedType(m_state.registers[argv].content);
- if (firstArg->isReferenceType()) {
- // We cannot know whether this will be a logging category at run time.
- // Therefore we always pass any object types as special last argument.
- addReadRegister(argv, m_typeResolver->globalType(
- m_typeResolver->genericType(firstArg)));
- } else {
- addReadRegister(argv, stringType);
- }
- }
+ m_state.setHasSideEffects(true);
+ setAccumulator(m_typeResolver->returnType(
+ m_typeResolver->voidType(), QQmlJSRegisterContent::MethodReturnValue,
+ m_typeResolver->consoleObject()));
+}
- for (int i = 1; i < argc; ++i)
- addReadRegister(argv + i, stringType);
+void QQmlJSTypePropagator::generate_callProperty_SAcheck(const QString propertyName, const QQmlJSScope::ConstPtr &baseType)
+{
+ // TODO: Should there be an analyzeCall() in the future? (w. corresponding onCall in Pass)
+ QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeRead(
+ QQmlJSScope::createQQmlSAElement(baseType), propertyName,
+ QQmlJSScope::createQQmlSAElement(m_function->qmlScope.containedType()),
+ QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
+ getCurrentBindingSourceLocation()));
+}
- m_state.setHasSideEffects(true);
- setAccumulator(voidType);
+void QQmlJSTypePropagator::generate_CallProperty(int nameIndex, int base, int argc, int argv)
+{
+ Q_ASSERT(m_state.registers.contains(base));
+ const auto callBase = m_state.registers[base].content;
+ const QString propertyName = m_jsUnitGenerator->stringForIndex(nameIndex);
+
+ if (m_typeResolver->registerContains(callBase, m_typeResolver->mathObject())) {
+ generate_CallProperty_SCMath(base, argc, argv);
return;
}
- if (m_typeResolver->registerContains(callBase, m_typeResolver->jsValueType())
- || m_typeResolver->registerContains(callBase, m_typeResolver->varType())) {
- const auto jsValueType = m_typeResolver->globalType(m_typeResolver->jsValueType());
- addReadRegister(base, jsValueType);
- for (int i = 0; i < argc; ++i)
- addReadRegister(argv + i, jsValueType);
- setAccumulator(jsValueType);
- m_state.setHasSideEffects(true);
+ if (m_typeResolver->registerContains(callBase, m_typeResolver->consoleObject()) && isLoggingMethod(propertyName)) {
+ generate_CallProperty_SCconsole(base, argc, argv);
return;
}
+ const auto baseType = callBase.containedType();
const auto member = m_typeResolver->memberType(callBase, propertyName);
+
if (!member.isMethod()) {
+ if (m_typeResolver->registerContains(callBase, m_typeResolver->jsValueType())
+ || m_typeResolver->registerContains(callBase, m_typeResolver->varType())) {
+ const auto jsValueType = m_typeResolver->globalType(m_typeResolver->jsValueType());
+ addReadRegister(base, jsValueType);
+ for (int i = 0; i < argc; ++i)
+ addReadRegister(argv + i, jsValueType);
+ m_state.setHasSideEffects(true);
+ setAccumulator(m_typeResolver->returnType(
+ m_typeResolver->jsValueType(), QQmlJSRegisterContent::JavaScriptReturnValue,
+ m_typeResolver->jsValueType()));
+ return;
+ }
+
setError(u"Type %1 does not have a property %2 for calling"_s
.arg(callBase.descriptiveName(), propertyName));
if (callBase.isType() && isCallingProperty(callBase.type(), propertyName))
return;
- if (isRestricted(propertyName))
+ if (checkForEnumProblems(callBase, propertyName))
return;
- std::optional<FixSuggestion> fixSuggestion;
-
- const auto baseType = m_typeResolver->containedType(callBase);
+ std::optional<QQmlJSFixSuggestion> fixSuggestion;
if (auto suggestion = QQmlJSUtils::didYouMean(propertyName, baseType->methods().keys(),
getCurrentSourceLocation());
@@ -1117,20 +1246,16 @@ void QQmlJSTypePropagator::generate_CallProperty(int nameIndex, int base, int ar
fixSuggestion = suggestion;
}
- m_logger->log(u"Property \"%1\" not found on type \"%2\""_s.arg(
- propertyName, m_typeResolver->containedTypeName(callBase, true)),
+ m_logger->log(u"Member \"%1\" not found on type \"%2\""_s.arg(
+ propertyName, callBase.containedTypeName()),
qmlMissingProperty, getCurrentSourceLocation(), true, true, fixSuggestion);
return;
}
- checkDeprecated(m_typeResolver->containedType(callBase), propertyName, true);
+ checkDeprecated(baseType, propertyName, true);
- if (m_passManager != nullptr) {
- // TODO: Should there be an analyzeCall() in the future? (w. corresponding onCall in Pass)
- m_passManager->analyzeRead(
- m_typeResolver->containedType(callBase),
- propertyName, m_function->qmlScope, getCurrentSourceLocation());
- }
+ if (m_passManager != nullptr)
+ generate_callProperty_SAcheck(propertyName, baseType);
addReadRegister(base, callBase);
@@ -1141,6 +1266,12 @@ void QQmlJSTypePropagator::generate_CallProperty(int nameIndex, int base, int ar
}
}
+ if (baseType->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence
+ && m_typeResolver->equals(member.scopeType(), m_typeResolver->arrayPrototype())
+ && propagateArrayMethod(propertyName, argc, argv, callBase)) {
+ return;
+ }
+
propagateCall(member.method(), argc, argv, member.scopeType());
}
@@ -1148,10 +1279,13 @@ QQmlJSMetaMethod QQmlJSTypePropagator::bestMatchForCall(const QList<QQmlJSMetaMe
int argc, int argv, QStringList *errors)
{
QQmlJSMetaMethod javascriptFunction;
+ QQmlJSMetaMethod candidate;
+ bool hasMultipleCandidates = false;
+
for (const auto &method : methods) {
// If we encounter a JavaScript function, use this as a fallback if no other method matches
- if (method.isJavaScriptFunction())
+ if (method.isJavaScriptFunction() && !javascriptFunction.isValid())
javascriptFunction = method;
if (method.returnType().isNull() && !method.returnTypeName().isEmpty()) {
@@ -1168,33 +1302,55 @@ QQmlJSMetaMethod QQmlJSTypePropagator::bestMatchForCall(const QList<QQmlJSMetaMe
continue;
}
- bool matches = true;
+ bool fuzzyMatch = true;
+ bool exactMatch = true;
for (int i = 0; i < argc; ++i) {
const auto argumentType = arguments[i].type();
if (argumentType.isNull()) {
errors->append(
u"type %1 for argument %2 cannot be resolved"_s.arg(arguments[i].typeName())
.arg(i));
- matches = false;
+ exactMatch = false;
+ fuzzyMatch = false;
break;
}
- if (canConvertFromTo(m_state.registers[argv + i].content,
- m_typeResolver->globalType(argumentType))) {
+ const auto content = m_state.registers[argv + i].content;
+ if (m_typeResolver->registerContains(content, argumentType))
+ continue;
+
+ exactMatch = false;
+ if (canConvertFromTo(content, m_typeResolver->globalType(argumentType)))
+ continue;
+
+ // We can try to call a method that expects a derived type.
+ if (argumentType->isReferenceType()
+ && m_typeResolver->inherits(
+ argumentType->baseType(), content.containedType())) {
continue;
}
errors->append(
u"argument %1 contains %2 but is expected to contain the type %3"_s.arg(i).arg(
- m_state.registers[argv + i].content.descriptiveName(),
- arguments[i].typeName()));
- matches = false;
+ content.descriptiveName(), arguments[i].typeName()));
+ fuzzyMatch = false;
break;
}
- if (matches)
+
+ if (exactMatch) {
return method;
+ } else if (fuzzyMatch) {
+ if (!candidate.isValid())
+ candidate = method;
+ else
+ hasMultipleCandidates = true;
+ }
}
- return javascriptFunction;
+
+ if (hasMultipleCandidates)
+ return QQmlJSMetaMethod();
+
+ return candidate.isValid() ? candidate : javascriptFunction;
}
void QQmlJSTypePropagator::setAccumulator(const QQmlJSRegisterContent &content)
@@ -1208,7 +1364,7 @@ void QQmlJSTypePropagator::setRegister(int index, const QQmlJSRegisterContent &c
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))) {
+ if (m_typeResolver->registerContains(lastTry, content.containedType())) {
m_state.setRegister(index, lastTry);
return;
}
@@ -1221,9 +1377,14 @@ void QQmlJSTypePropagator::mergeRegister(
int index, const QQmlJSRegisterContent &a, const QQmlJSRegisterContent &b)
{
auto merged = m_typeResolver->merge(a, b);
-
Q_ASSERT(merged.isValid());
- Q_ASSERT(merged.isConversion());
+
+ if (!merged.isConversion()) {
+ // The registers were the same. We're already tracking them.
+ m_state.annotations[currentInstructionOffset()].typeConversions[index].content = merged;
+ m_state.registers[index].content = merged;
+ return;
+ }
auto tryPrevStateConversion = [this](int index, const QQmlJSRegisterContent &merged) -> bool {
auto it = m_prevStateAnnotations.find(currentInstructionOffset());
@@ -1234,19 +1395,20 @@ void QQmlJSTypePropagator::mergeRegister(
if (conversion == it->second.typeConversions.end())
return false;
- const QQmlJSRegisterContent &lastTry = conversion.value().content;
+ const VirtualRegister &lastTry = conversion.value();
- Q_ASSERT(lastTry.isValid());
- Q_ASSERT(lastTry.isConversion());
+ Q_ASSERT(lastTry.content.isValid());
+ if (!lastTry.content.isConversion())
+ return false;
- if (!m_typeResolver->equals(lastTry.conversionResult(), merged.conversionResult())
- || lastTry.conversionOrigins() != merged.conversionOrigins()) {
+ if (!m_typeResolver->equals(lastTry.content.conversionResult(), merged.conversionResult())
+ || lastTry.content.conversionOrigins() != merged.conversionOrigins()) {
return false;
}
// We don't need to track it again if we've come to the same conclusion before.
- m_state.annotations[currentInstructionOffset()].typeConversions[index].content = lastTry;
- m_state.registers[index].content = lastTry;
+ m_state.annotations[currentInstructionOffset()].typeConversions[index] = lastTry;
+ m_state.registers[index] = lastTry;
return true;
};
@@ -1272,11 +1434,15 @@ void QQmlJSTypePropagator::propagateCall(
const QQmlJSMetaMethod match = bestMatchForCall(methods, argc, argv, &errors);
if (!match.isValid()) {
- Q_ASSERT(errors.size() == methods.size());
- if (methods.size() == 1)
+ if (methods.size() == 1) {
+ // Cannot have multiple fuzzy matches if there is only one method
+ Q_ASSERT(errors.size() == 1);
setError(errors.first());
- else
+ } else if (errors.size() < methods.size()) {
+ setError(u"Multiple matching overrides found. Cannot determine the right one."_s);
+ } else {
setError(u"No matching override found. Candidates:\n"_s + errors.join(u'\n'));
+ }
return;
}
@@ -1292,7 +1458,6 @@ void QQmlJSTypePropagator::propagateCall(
if (!m_state.accumulatorOut().isValid())
setError(u"Cannot store return type of method %1()."_s.arg(match.methodName()));
- m_state.setHasSideEffects(true);
const auto types = match.parameters();
for (int i = 0; i < argc; ++i) {
if (i < types.size()) {
@@ -1306,6 +1471,7 @@ void QQmlJSTypePropagator::propagateCall(
}
addReadRegister(argv + i, m_typeResolver->globalType(m_typeResolver->jsValueType()));
}
+ m_state.setHasSideEffects(true);
}
bool QQmlJSTypePropagator::propagateTranslationMethod(
@@ -1316,7 +1482,7 @@ bool QQmlJSTypePropagator::propagateTranslationMethod(
const QQmlJSMetaMethod method = methods.front();
const QQmlJSRegisterContent intType
- = m_typeResolver->globalType(m_typeResolver->intType());
+ = m_typeResolver->globalType(m_typeResolver->int32Type());
const QQmlJSRegisterContent stringType
= m_typeResolver->globalType(m_typeResolver->stringType());
const QQmlJSRegisterContent returnType
@@ -1423,24 +1589,196 @@ void QQmlJSTypePropagator::propagateStringArgCall(int argv)
m_typeResolver->stringType()));
Q_ASSERT(m_state.accumulatorOut().isValid());
- const QQmlJSScope::ConstPtr input = m_typeResolver->containedType(
- m_state.registers[argv].content);
- for (QQmlJSScope::ConstPtr targetType : {
- m_typeResolver->intType(),
- m_typeResolver->uintType(),
- m_typeResolver->realType(),
- m_typeResolver->floatType(),
- m_typeResolver->boolType(),
- }) {
- if (m_typeResolver->equals(input, targetType)) {
- addReadRegister(argv, m_typeResolver->globalType(targetType));
- return;
- }
+ const QQmlJSScope::ConstPtr input = m_state.registers[argv].content.containedType();
+
+ if (m_typeResolver->equals(input, m_typeResolver->uint32Type())) {
+ addReadRegister(argv, m_typeResolver->globalType(m_typeResolver->realType()));
+ return;
+ }
+
+ if (m_typeResolver->isIntegral(input)) {
+ addReadRegister(argv, m_typeResolver->globalType(m_typeResolver->int32Type()));
+ return;
+ }
+
+ if (m_typeResolver->isNumeric(input)) {
+ addReadRegister(argv, m_typeResolver->globalType(m_typeResolver->realType()));
+ return;
+ }
+
+ if (m_typeResolver->equals(input, m_typeResolver->boolType())) {
+ addReadRegister(argv, m_typeResolver->globalType(m_typeResolver->boolType()));
+ return;
}
addReadRegister(argv, m_typeResolver->globalType(m_typeResolver->stringType()));
}
+bool QQmlJSTypePropagator::propagateArrayMethod(
+ const QString &name, int argc, int argv, const QQmlJSRegisterContent &baseType)
+{
+ // TODO:
+ // * For concat() we need to decide what kind of array to return and what kinds of arguments to
+ // accept.
+ // * For entries(), keys(), and values() we need iterators.
+ // * For find(), findIndex(), sort(), every(), some(), forEach(), map(), filter(), reduce(),
+ // and reduceRight() we need typed function pointers.
+
+ const auto intType = m_typeResolver->globalType(m_typeResolver->int32Type());
+ const auto stringType = m_typeResolver->globalType(m_typeResolver->stringType());
+ const auto baseContained = baseType.containedType();
+ const auto valueContained = baseContained->valueType();
+ const auto valueType = m_typeResolver->globalType(valueContained);
+
+ const bool canHaveSideEffects = (baseType.isProperty() && baseType.isWritable())
+ || baseContained->isListProperty()
+ || baseType.isConversion();
+
+ const auto setReturnType = [&](const QQmlJSScope::ConstPtr type) {
+ setAccumulator(m_typeResolver->returnType(
+ type, QQmlJSRegisterContent::MethodReturnValue, baseContained));
+ };
+
+ if (name == u"copyWithin" && argc > 0 && argc < 4) {
+ for (int i = 0; i < argc; ++i) {
+ if (!canConvertFromTo(m_state.registers[argv + i].content, intType))
+ return false;
+ }
+
+ for (int i = 0; i < argc; ++i)
+ addReadRegister(argv + i, intType);
+
+ m_state.setHasSideEffects(canHaveSideEffects);
+ setReturnType(baseContained);
+ return true;
+ }
+
+ if (name == u"fill" && argc > 0 && argc < 4) {
+ if (!canConvertFromTo(m_state.registers[argv].content, valueType))
+ return false;
+
+ for (int i = 1; i < argc; ++i) {
+ if (!canConvertFromTo(m_state.registers[argv + i].content, intType))
+ return false;
+ }
+
+ addReadRegister(argv, valueType);
+
+ for (int i = 1; i < argc; ++i)
+ addReadRegister(argv + i, intType);
+
+ m_state.setHasSideEffects(canHaveSideEffects);
+ setReturnType(baseContained);
+ return true;
+ }
+
+ if (name == u"includes" && argc > 0 && argc < 3) {
+ if (!canConvertFromTo(m_state.registers[argv].content, valueType))
+ return false;
+
+ if (argc == 2) {
+ if (!canConvertFromTo(m_state.registers[argv + 1].content, intType))
+ return false;
+ addReadRegister(argv + 1, intType);
+ }
+
+ addReadRegister(argv, valueType);
+ setReturnType(m_typeResolver->boolType());
+ return true;
+ }
+
+ if (name == u"toString" || (name == u"join" && argc < 2)) {
+ if (argc == 1) {
+ if (!canConvertFromTo(m_state.registers[argv].content, stringType))
+ return false;
+ addReadRegister(argv, stringType);
+ }
+
+ setReturnType(m_typeResolver->stringType());
+ return true;
+ }
+
+ if ((name == u"pop" || name == u"shift") && argc == 0) {
+ m_state.setHasSideEffects(canHaveSideEffects);
+ setReturnType(valueContained);
+ return true;
+ }
+
+ if (name == u"push" || name == u"unshift") {
+ for (int i = 0; i < argc; ++i) {
+ if (!canConvertFromTo(m_state.registers[argv + i].content, valueType))
+ return false;
+ }
+
+ for (int i = 0; i < argc; ++i)
+ addReadRegister(argv + i, valueType);
+
+ m_state.setHasSideEffects(canHaveSideEffects);
+ setReturnType(m_typeResolver->int32Type());
+ return true;
+ }
+
+ if (name == u"reverse" && argc == 0) {
+ m_state.setHasSideEffects(canHaveSideEffects);
+ setReturnType(baseContained);
+ return true;
+ }
+
+ if (name == u"slice" && argc < 3) {
+ for (int i = 0; i < argc; ++i) {
+ if (!canConvertFromTo(m_state.registers[argv + i].content, intType))
+ return false;
+ }
+
+ for (int i = 0; i < argc; ++i)
+ addReadRegister(argv + i, intType);
+
+ setReturnType(baseType.containedType()->isListProperty()
+ ? m_typeResolver->qObjectListType()
+ : baseContained);
+ return true;
+ }
+
+ if (name == u"splice" && argc > 0) {
+ for (int i = 0; i < 2; ++i) {
+ if (!canConvertFromTo(m_state.registers[argv + i].content, intType))
+ return false;
+ }
+
+ for (int i = 2; i < argc; ++i) {
+ if (!canConvertFromTo(m_state.registers[argv + i].content, valueType))
+ return false;
+ }
+
+ for (int i = 0; i < 2; ++i)
+ addReadRegister(argv + i, intType);
+
+ for (int i = 2; i < argc; ++i)
+ addReadRegister(argv + i, valueType);
+
+ m_state.setHasSideEffects(canHaveSideEffects);
+ setReturnType(baseContained);
+ return true;
+ }
+
+ if ((name == u"indexOf" || name == u"lastIndexOf") && argc > 0 && argc < 3) {
+ if (!canConvertFromTo(m_state.registers[argv].content, valueType))
+ return false;
+
+ if (argc == 2) {
+ if (!canConvertFromTo(m_state.registers[argv + 1].content, intType))
+ return false;
+ addReadRegister(argv + 1, intType);
+ }
+
+ addReadRegister(argv, valueType);
+ setReturnType(m_typeResolver->int32Type());
+ return true;
+ }
+
+ return false;
+}
+
void QQmlJSTypePropagator::generate_CallPropertyLookup(int lookupIndex, int base, int argc,
int argv)
{
@@ -1494,7 +1832,7 @@ void QQmlJSTypePropagator::generate_CallQmlContextPropertyLookup(int index, int
{
const QString name = m_jsUnitGenerator->lookupName(index);
propagateScopeLookupCall(name, argc, argv);
- checkDeprecated(m_function->qmlScope, name, true);
+ checkDeprecated(m_function->qmlScope.containedType(), name, true);
}
void QQmlJSTypePropagator::generate_CallWithSpread(int func, int thisObject, int argc, int argv)
@@ -1517,14 +1855,70 @@ void QQmlJSTypePropagator::generate_TailCall(int func, int thisObject, int argc,
INSTR_PROLOGUE_NOT_IMPLEMENTED();
}
+void QQmlJSTypePropagator::generate_Construct_SCDate(int argc, int argv)
+{
+ setAccumulator(m_typeResolver->globalType(m_typeResolver->dateTimeType()));
+
+ if (argc == 1) {
+ const QQmlJSRegisterContent argType = m_state.registers[argv].content;
+ if (m_typeResolver->isNumeric(argType)) {
+ addReadRegister(
+ argv, m_typeResolver->globalType(m_typeResolver->realType()));
+ } else if (m_typeResolver->registerContains(argType, m_typeResolver->stringType())) {
+ addReadRegister(
+ argv, m_typeResolver->globalType(m_typeResolver->stringType()));
+ } else if (m_typeResolver->registerContains(argType, m_typeResolver->dateTimeType())
+ || m_typeResolver->registerContains(argType, m_typeResolver->dateType())
+ || m_typeResolver->registerContains(argType, m_typeResolver->timeType())) {
+ addReadRegister(
+ argv, m_typeResolver->globalType(m_typeResolver->dateTimeType()));
+ } else {
+ addReadRegister(
+ argv, m_typeResolver->globalType(m_typeResolver->jsPrimitiveType()));
+ }
+ } else {
+ constexpr int maxArgc = 7; // year, month, day, hours, minutes, seconds, milliseconds
+ for (int i = 0; i < std::min(argc, maxArgc); ++i) {
+ addReadRegister(
+ argv + i, m_typeResolver->globalType(m_typeResolver->realType()));
+ }
+ }
+}
+
+void QQmlJSTypePropagator::generate_Construct_SCArray(int argc, int argv)
+{
+ if (argc == 1) {
+ if (m_typeResolver->isNumeric(m_state.registers[argv].content)) {
+ setAccumulator(m_typeResolver->globalType(m_typeResolver->variantListType()));
+ addReadRegister(argv, m_typeResolver->globalType(m_typeResolver->realType()));
+ } else {
+ generate_DefineArray(argc, argv);
+ }
+ } else {
+ generate_DefineArray(argc, argv);
+ }
+}
void QQmlJSTypePropagator::generate_Construct(int func, int argc, int argv)
{
- m_state.setHasSideEffects(true);
- Q_UNUSED(func)
- Q_UNUSED(argv)
+ const QQmlJSRegisterContent type = m_state.registers[func].content;
+ if (!type.isMethod()) {
+ m_state.setHasSideEffects(true);
+ setAccumulator(m_typeResolver->globalType(m_typeResolver->jsValueType()));
+ return;
+ }
- Q_UNUSED(argc)
+ if (type.method() == m_typeResolver->jsGlobalObject()->methods(u"Date"_s)) {
+ generate_Construct_SCDate(argc, argv);
+ return;
+ }
+ if (type.method() == m_typeResolver->jsGlobalObject()->methods(u"Array"_s)) {
+ generate_Construct_SCArray(argc, argv);
+
+ return;
+ }
+
+ m_state.setHasSideEffects(true);
setAccumulator(m_typeResolver->globalType(m_typeResolver->jsValueType()));
}
@@ -1560,8 +1954,22 @@ void QQmlJSTypePropagator::generate_UnwindToLabel(int level, int offset)
void QQmlJSTypePropagator::generate_DeadTemporalZoneCheck(int name)
{
- Q_UNUSED(name)
- INSTR_PROLOGUE_NOT_IMPLEMENTED();
+ const auto fail = [this, name]() {
+ setError(u"Cannot statically assert the dead temporal zone check for %1"_s.arg(
+ name ? m_jsUnitGenerator->stringForIndex(name) : u"the anonymous accumulator"_s));
+ };
+
+ const QQmlJSRegisterContent in = m_state.accumulatorIn();
+ if (in.isConversion()) {
+ for (const QQmlJSScope::ConstPtr &origin : in.conversionOrigins()) {
+ if (!m_typeResolver->equals(origin, m_typeResolver->emptyType()))
+ continue;
+ fail();
+ break;
+ }
+ } else if (m_typeResolver->registerContains(in, m_typeResolver->emptyType())) {
+ fail();
+ }
}
void QQmlJSTypePropagator::generate_ThrowException()
@@ -1634,28 +2042,41 @@ void QQmlJSTypePropagator::generate_PopContext()
void QQmlJSTypePropagator::generate_GetIterator(int iterator)
{
- Q_UNUSED(iterator)
- INSTR_PROLOGUE_NOT_IMPLEMENTED();
+ const QQmlJSRegisterContent listType = m_state.accumulatorIn();
+ if (!listType.isList()) {
+ const auto jsValue = m_typeResolver->globalType(m_typeResolver->jsValueType());
+ addReadAccumulator(jsValue);
+ setAccumulator(jsValue);
+ return;
+ }
+
+ addReadAccumulator(listType);
+ setAccumulator(m_typeResolver->iteratorPointer(
+ listType, QQmlJS::AST::ForEachType(iterator), currentInstructionOffset()));
}
-void QQmlJSTypePropagator::generate_IteratorNext(int value, int done)
+void QQmlJSTypePropagator::generate_IteratorNext(int value, int offset)
{
- Q_UNUSED(value)
- Q_UNUSED(done)
- INSTR_PROLOGUE_NOT_IMPLEMENTED();
+ const QQmlJSRegisterContent iteratorType = m_state.accumulatorIn();
+ addReadAccumulator(iteratorType);
+ setRegister(value, m_typeResolver->merge(
+ m_typeResolver->valueType(iteratorType),
+ m_typeResolver->globalType(m_typeResolver->voidType())));
+ saveRegisterStateForJump(offset);
+ m_state.setHasSideEffects(true);
}
-void QQmlJSTypePropagator::generate_IteratorNextForYieldStar(int iterator, int object)
+void QQmlJSTypePropagator::generate_IteratorNextForYieldStar(int iterator, int object, int offset)
{
Q_UNUSED(iterator)
Q_UNUSED(object)
+ Q_UNUSED(offset)
INSTR_PROLOGUE_NOT_IMPLEMENTED();
}
-void QQmlJSTypePropagator::generate_IteratorClose(int done)
+void QQmlJSTypePropagator::generate_IteratorClose()
{
- Q_UNUSED(done)
- INSTR_PROLOGUE_NOT_IMPLEMENTED();
+ // Noop
}
void QQmlJSTypePropagator::generate_DestructureRestElement()
@@ -1696,9 +2117,7 @@ void QQmlJSTypePropagator::generate_DeclareVar(int varName, int isDeletable)
void QQmlJSTypePropagator::generate_DefineArray(int argc, int args)
{
- setAccumulator(m_typeResolver->globalType(argc == 0
- ? m_typeResolver->emptyListType()
- : m_typeResolver->variantListType()));
+ setAccumulator(m_typeResolver->globalType(m_typeResolver->variantListType()));
// Track all arguments as the same type.
const QQmlJSRegisterContent elementType
@@ -1709,12 +2128,34 @@ void QQmlJSTypePropagator::generate_DefineArray(int argc, int args)
void QQmlJSTypePropagator::generate_DefineObjectLiteral(int internalClassId, int argc, int args)
{
- // TODO: computed property names, getters, and setters are unsupported. How do we catch them?
+ const int classSize = m_jsUnitGenerator->jsClassSize(internalClassId);
+ Q_ASSERT(argc >= classSize);
- Q_UNUSED(internalClassId)
- Q_UNUSED(argc)
- Q_UNUSED(args)
- setAccumulator(m_typeResolver->globalType(m_typeResolver->jsValueType()));
+ // Track each element as separate type
+ for (int i = 0; i < classSize; ++i) {
+ addReadRegister(
+ args + i,
+ m_typeResolver->tracked(m_typeResolver->globalType(m_typeResolver->varType())));
+ }
+
+ for (int i = classSize; i < argc; i += 3) {
+ // layout for remaining members is:
+ // 0: ObjectLiteralArgument - Value|Method|Getter|Setter
+ // We cannot do anything useful with this. Any code that would call a getter/setter/method
+ // could not be compiled to C++. Ignore it.
+
+ // 1: name of argument
+ addReadRegister(
+ args + i + 1,
+ m_typeResolver->tracked(m_typeResolver->globalType(m_typeResolver->stringType())));
+
+ // 2: value of argument
+ addReadRegister(
+ args + i + 2,
+ m_typeResolver->tracked(m_typeResolver->globalType(m_typeResolver->varType())));
+ }
+
+ setAccumulator(m_typeResolver->globalType(m_typeResolver->variantMapType()));
}
void QQmlJSTypePropagator::generate_CreateClass(int classIndex, int heritage, int computedNames)
@@ -1743,7 +2184,7 @@ void QQmlJSTypePropagator::generate_CreateRestParameter(int argIndex)
void QQmlJSTypePropagator::generate_ConvertThisToObject()
{
- INSTR_PROLOGUE_NOT_IMPLEMENTED();
+ setRegister(This, m_typeResolver->globalType(m_typeResolver->qObjectType()));
}
void QQmlJSTypePropagator::generate_LoadSuperConstructor()
@@ -1772,8 +2213,8 @@ void QQmlJSTypePropagator::generate_JumpTrue(int offset)
return;
}
saveRegisterStateForJump(offset);
- m_state.setHasSideEffects(true);
addReadAccumulator(m_typeResolver->globalType(m_typeResolver->boolType()));
+ m_state.setHasSideEffects(true);
}
void QQmlJSTypePropagator::generate_JumpFalse(int offset)
@@ -1785,8 +2226,8 @@ void QQmlJSTypePropagator::generate_JumpFalse(int offset)
return;
}
saveRegisterStateForJump(offset);
- m_state.setHasSideEffects(true);
addReadAccumulator(m_typeResolver->globalType(m_typeResolver->boolType()));
+ m_state.setHasSideEffects(true);
}
void QQmlJSTypePropagator::generate_JumpNoException(int offset)
@@ -1810,7 +2251,7 @@ 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()) {
+ || m_state.accumulatorIn().containedType()->isReferenceType()) {
addReadAccumulator(m_state.accumulatorIn());
} else {
addReadAccumulator(m_typeResolver->globalType(m_typeResolver->jsPrimitiveType()));
@@ -1819,7 +2260,7 @@ void QQmlJSTypePropagator::recordEqualsNullType()
void QQmlJSTypePropagator::recordEqualsIntType()
{
// We have specializations for numeric types and bool.
- const QQmlJSScope::ConstPtr in = m_typeResolver->containedType(m_state.accumulatorIn());
+ const QQmlJSScope::ConstPtr in = m_state.accumulatorIn().containedType();
if (m_typeResolver->registerContains(m_state.accumulatorIn(), m_typeResolver->boolType())
|| m_typeResolver->isNumeric(m_state.accumulatorIn())) {
addReadAccumulator(m_state.accumulatorIn());
@@ -1833,42 +2274,33 @@ void QQmlJSTypePropagator::recordEqualsType(int lhs)
return content.isEnumeration() || m_typeResolver->isNumeric(content);
};
- const auto isIntCompatible = [this](const QQmlJSRegisterContent &content) {
- const QQmlJSScope::ConstPtr contained = m_typeResolver->containedType(content);
- return contained->scopeType() == QQmlJSScope::EnumScope
- || m_typeResolver->equals(contained, m_typeResolver->intType())
- || m_typeResolver->equals(contained, m_typeResolver->uintType());
- };
-
const auto accumulatorIn = m_state.accumulatorIn();
const auto lhsRegister = m_state.registers[lhs].content;
// If the types are primitive, we compare directly ...
if (m_typeResolver->isPrimitive(accumulatorIn) || accumulatorIn.isEnumeration()) {
if (m_typeResolver->registerContains(
- accumulatorIn, m_typeResolver->containedType(lhsRegister))) {
- addReadRegister(lhs, accumulatorIn);
+ accumulatorIn, lhsRegister.containedType())
+ || (isNumericOrEnum(accumulatorIn) && isNumericOrEnum(lhsRegister))
+ || m_typeResolver->isPrimitive(lhsRegister)) {
+ addReadRegister(lhs, lhsRegister);
addReadAccumulator(accumulatorIn);
return;
- } else if (isNumericOrEnum(accumulatorIn) && isNumericOrEnum(lhsRegister)) {
- const auto targetType = isIntCompatible(accumulatorIn) && isIntCompatible(lhsRegister)
- ? m_typeResolver->globalType(m_typeResolver->intType())
- : m_typeResolver->globalType(m_typeResolver->realType());
- addReadRegister(lhs, targetType);
- addReadAccumulator(targetType);
- return;
- } else if (m_typeResolver->isPrimitive(lhsRegister)) {
- const QQmlJSRegisterContent primitive = m_typeResolver->globalType(
- m_typeResolver->jsPrimitiveType());
- addReadRegister(lhs, primitive);
- addReadAccumulator(primitive);
- return;
}
}
- // We don't modify types if the types are comparable with QObject or var
- if (canStrictlyCompareWithVar(m_typeResolver, lhsRegister, accumulatorIn)
- || canCompareWithQObject(m_typeResolver, lhsRegister, accumulatorIn)) {
+ const auto containedAccumulatorIn = m_typeResolver->isOptionalType(accumulatorIn)
+ ? m_typeResolver->extractNonVoidFromOptionalType(accumulatorIn)
+ : accumulatorIn.containedType();
+
+ const auto containedLhs = m_typeResolver->isOptionalType(lhsRegister)
+ ? m_typeResolver->extractNonVoidFromOptionalType(lhsRegister)
+ : lhsRegister.containedType();
+
+ // We don't modify types if the types are comparable with QObject, QUrl or var types
+ if (canStrictlyCompareWithVar(m_typeResolver, containedLhs, containedAccumulatorIn)
+ || canCompareWithQObject(m_typeResolver, containedLhs, containedAccumulatorIn)
+ || canCompareWithQUrl(m_typeResolver, containedLhs, containedAccumulatorIn)) {
addReadRegister(lhs, lhsRegister);
addReadAccumulator(accumulatorIn);
return;
@@ -1912,7 +2344,7 @@ void QQmlJSTypePropagator::generate_CmpEqInt(int lhsConst)
recordEqualsIntType();
Q_UNUSED(lhsConst)
setAccumulator(QQmlJSRegisterContent(m_typeResolver->typeForBinaryOperation(
- QSOperator::Op::Equal, m_typeResolver->globalType(m_typeResolver->intType()),
+ QSOperator::Op::Equal, m_typeResolver->globalType(m_typeResolver->int32Type()),
m_state.accumulatorIn())));
}
@@ -1921,7 +2353,7 @@ void QQmlJSTypePropagator::generate_CmpNeInt(int lhsConst)
recordEqualsIntType();
Q_UNUSED(lhsConst)
setAccumulator(QQmlJSRegisterContent(m_typeResolver->typeForBinaryOperation(
- QSOperator::Op::NotEqual, m_typeResolver->globalType(m_typeResolver->intType()),
+ QSOperator::Op::NotEqual, m_typeResolver->globalType(m_typeResolver->int32Type()),
m_state.accumulatorIn())));
}
@@ -1994,32 +2426,53 @@ void QQmlJSTypePropagator::generate_CmpInstanceOf(int lhs)
void QQmlJSTypePropagator::generate_As(int lhs)
{
const QQmlJSRegisterContent input = checkedInputRegister(lhs);
- QQmlJSScope::ConstPtr contained;
+ const QQmlJSScope::ConstPtr inContained = input.containedType();
+
+ QQmlJSScope::ConstPtr outContained;
switch (m_state.accumulatorIn().variant()) {
case QQmlJSRegisterContent::ScopeAttached:
- contained = m_state.accumulatorIn().scopeType();
+ outContained = m_state.accumulatorIn().scopeType();
break;
case QQmlJSRegisterContent::MetaType:
- contained = m_state.accumulatorIn().scopeType();
- if (contained->isComposite()) // Otherwise we don't need it
+ outContained = m_state.accumulatorIn().scopeType();
+ if (outContained->isComposite()) // Otherwise we don't need it
addReadAccumulator(m_typeResolver->globalType(m_typeResolver->metaObjectType()));
break;
default:
- contained = m_typeResolver->containedType(m_state.accumulatorIn());
+ outContained = m_state.accumulatorIn().containedType();
break;
}
- addReadRegister(lhs, m_typeResolver->globalType(contained));
+ QQmlJSRegisterContent output;
- if (m_typeResolver->containedType(input)->accessSemantics()
- != QQmlJSScope::AccessSemantics::Reference
- || contained->accessSemantics() != QQmlJSScope::AccessSemantics::Reference) {
+ if (outContained->accessSemantics() == QQmlJSScope::AccessSemantics::Reference) {
+ // A referece type cast can result in either the type or null.
+ // Reference types can hold null. We don't need to special case that.
+
+ if (m_typeResolver->inherits(inContained, outContained))
+ output = input;
+ else
+ output = m_typeResolver->cast(input, outContained);
+ } else if (!m_typeResolver->canAddressValueTypes()) {
setError(u"invalid cast from %1 to %2. You can only cast object types."_s
- .arg(input.descriptiveName(), m_state.accumulatorIn().descriptiveName()));
+ .arg(input.descriptiveName(), m_state.accumulatorIn().descriptiveName()));
+ return;
} else {
- setAccumulator(m_typeResolver->globalType(contained));
+ if (m_typeResolver->inherits(inContained, outContained)) {
+ // A "slicing" cannot result in void
+ output = m_typeResolver->cast(input, outContained);
+ } else {
+ // A value type cast can result in either the type or undefined.
+ // Using convert() retains the variant of the input type.
+ output = m_typeResolver->merge(
+ m_typeResolver->cast(input, outContained),
+ m_typeResolver->cast(input, m_typeResolver->voidType()));
+ }
}
+
+ addReadRegister(lhs, input);
+ setAccumulator(output);
}
void QQmlJSTypePropagator::checkConversion(
@@ -2085,7 +2538,7 @@ void QQmlJSTypePropagator::generateBinaryConstArithmeticOperation(QSOperator::Op
{
const QQmlJSRegisterContent type = m_typeResolver->typeForBinaryOperation(
op, m_state.accumulatorIn(),
- m_typeResolver->builtinType(m_typeResolver->intType()));
+ m_typeResolver->builtinType(m_typeResolver->int32Type()));
checkConversion(m_state.accumulatorIn(), type);
addReadAccumulator(type);
@@ -2190,9 +2643,8 @@ void QQmlJSTypePropagator::generate_Sub(int lhs)
void QQmlJSTypePropagator::generate_InitializeBlockDeadTemporalZone(int firstReg, int count)
{
- Q_UNUSED(firstReg)
- Q_UNUSED(count)
- // Ignore. We reject uninitialized values anyway.
+ for (int reg = firstReg, end = firstReg + count; reg < end; ++reg)
+ setRegister(reg, m_typeResolver->globalType(m_typeResolver->emptyType()));
}
void QQmlJSTypePropagator::generate_ThrowOnNullOrUndefined()
@@ -2206,31 +2658,6 @@ void QQmlJSTypePropagator::generate_GetTemplateObject(int index)
INSTR_PROLOGUE_NOT_IMPLEMENTED();
}
-static bool instructionManipulatesContext(QV4::Moth::Instr::Type type)
-{
- using Type = QV4::Moth::Instr::Type;
- switch (type) {
- case Type::PopContext:
- case Type::PopScriptContext:
- case Type::CreateCallContext:
- case Type::CreateCallContext_Wide:
- case Type::PushCatchContext:
- case Type::PushCatchContext_Wide:
- case Type::PushWithContext:
- case Type::PushWithContext_Wide:
- case Type::PushBlockContext:
- case Type::PushBlockContext_Wide:
- case Type::CloneBlockContext:
- case Type::CloneBlockContext_Wide:
- case Type::PushScriptContext:
- case Type::PushScriptContext_Wide:
- return true;
- default:
- break;
- }
- return false;
-}
-
QV4::Moth::ByteCodeHandler::Verdict
QQmlJSTypePropagator::startInstruction(QV4::Moth::Instr::Type type)
{
@@ -2298,9 +2725,6 @@ void QQmlJSTypePropagator::endInstruction(QV4::Moth::Instr::Type instr)
currentInstruction.readRegisters = m_state.takeReadRegisters();
currentInstruction.hasSideEffects = m_state.hasSideEffects();
currentInstruction.isRename = m_state.isRename();
- m_state.setHasSideEffects(false);
- m_state.setIsRename(false);
- m_state.setReadRegisters(VirtualRegisters());
switch (instr) {
// the following instructions are not expected to produce output in the accumulator
@@ -2324,6 +2748,10 @@ void QQmlJSTypePropagator::endInstruction(QV4::Moth::Instr::Type instr)
case QV4::Moth::Instr::Type::PushCatchContext:
case QV4::Moth::Instr::Type::UnwindDispatch:
case QV4::Moth::Instr::Type::InitializeBlockDeadTemporalZone:
+ case QV4::Moth::Instr::Type::ConvertThisToObject:
+ case QV4::Moth::Instr::Type::DeadTemporalZoneCheck:
+ case QV4::Moth::Instr::Type::IteratorNext:
+ case QV4::Moth::Instr::Type::IteratorNextForYieldStar:
if (m_state.changedRegisterIndex() == Accumulator && !m_error->isValid()) {
setError(u"Instruction is not expected to populate the accumulator"_s);
return;
@@ -2339,11 +2767,26 @@ void QQmlJSTypePropagator::endInstruction(QV4::Moth::Instr::Type instr)
}
}
+ if (!(m_error->isValid() && m_error->isError())
+ && instr != QV4::Moth::Instr::Type::DeadTemporalZoneCheck) {
+ // An instruction needs to have side effects or write to another register otherwise it's a
+ // noop. DeadTemporalZoneCheck is not needed by the compiler and is ignored.
+ Q_ASSERT(m_state.hasSideEffects() || m_state.changedRegisterIndex() != -1);
+ }
+
if (m_state.changedRegisterIndex() != InvalidRegister) {
Q_ASSERT(m_error->isValid() || m_state.changedRegister().isValid());
- m_state.registers[m_state.changedRegisterIndex()].content = m_state.changedRegister();
+ VirtualRegister &r = m_state.registers[m_state.changedRegisterIndex()];
+ r.content = m_state.changedRegister();
+ r.canMove = false;
+ r.affectedBySideEffects = m_state.isRename()
+ && m_state.isRegisterAffectedBySideEffects(m_state.renameSourceRegisterIndex());
m_state.clearChangedRegister();
}
+
+ m_state.setHasSideEffects(false);
+ m_state.setIsRename(false);
+ m_state.setReadRegisters(VirtualRegisters());
}
QQmlJSRegisterContent QQmlJSTypePropagator::propagateBinaryOperation(QSOperator::Op op, int lhs)
@@ -2356,12 +2799,6 @@ QQmlJSRegisterContent QQmlJSTypePropagator::propagateBinaryOperation(QSOperator:
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;
}
diff --git a/src/qmlcompiler/qqmljstypepropagator_p.h b/src/qmlcompiler/qqmljstypepropagator_p.h
index 550048767e..c9bbeb27cc 100644
--- a/src/qmlcompiler/qqmljstypepropagator_p.h
+++ b/src/qmlcompiler/qqmljstypepropagator_p.h
@@ -24,14 +24,14 @@ namespace QQmlSA {
class PassManager;
};
-struct Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSTypePropagator : public QQmlJSCompilePass
+struct Q_QMLCOMPILER_EXPORT QQmlJSTypePropagator : public QQmlJSCompilePass
{
QQmlJSTypePropagator(const QV4::Compiler::JSUnitGenerator *unitGenerator,
const QQmlJSTypeResolver *typeResolver, QQmlJSLogger *logger,
- QQmlJSTypeInfo *typeInfo = nullptr,
+ BasicBlocks basicBlocks = {}, InstructionAnnotations annotations = {},
QQmlSA::PassManager *passManager = nullptr);
- InstructionAnnotations run(const Function *m_function, QQmlJS::DiagnosticMessage *error);
+ BlocksAndAnnotations run(const Function *m_function, QQmlJS::DiagnosticMessage *error);
void generate_Ret() override;
void generate_Debug() override;
@@ -57,6 +57,7 @@ struct Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSTypePropagator : public QQmlJSCompileP
void generate_LoadName(int nameIndex) override;
void generate_LoadGlobalLookup(int index) override;
void generate_LoadQmlContextPropertyLookup(int index) override;
+ void generate_StoreNameCommon(int nameIndex);
void generate_StoreNameSloppy(int nameIndex) override;
void generate_StoreNameStrict(int name) override;
void generate_LoadElement(int base) override;
@@ -101,9 +102,9 @@ struct Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSTypePropagator : public QQmlJSCompileP
void generate_PopScriptContext() override;
void generate_PopContext() override;
void generate_GetIterator(int iterator) override;
- void generate_IteratorNext(int value, int done) override;
- void generate_IteratorNextForYieldStar(int iterator, int object) override;
- void generate_IteratorClose(int done) override;
+ void generate_IteratorNext(int value, int offset) override;
+ void generate_IteratorNextForYieldStar(int iterator, int object, int offset) override;
+ void generate_IteratorClose() override;
void generate_DestructureRestElement() override;
void generate_DeleteProperty(int base, int index) override;
void generate_DeleteName(int name) override;
@@ -168,6 +169,8 @@ struct Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSTypePropagator : public QQmlJSCompileP
void generate_ThrowOnNullOrUndefined() override;
void generate_GetTemplateObject(int index) override;
+ bool checkForEnumProblems(const QQmlJSRegisterContent &base, const QString &propertyName);
+
Verdict startInstruction(QV4::Moth::Instr::Type instr) override;
void endInstruction(QV4::Moth::Instr::Type instr) override;
@@ -188,7 +191,6 @@ private:
void handleUnqualifiedAccess(const QString &name, bool isMethod) const;
void checkDeprecated(QQmlJSScope::ConstPtr scope, const QString &name, bool isMethod) const;
- bool isRestricted(const QString &propertyName) const;
bool isCallingProperty(QQmlJSScope::ConstPtr scope, const QString &name) const;
enum PropertyResolution {
@@ -213,7 +215,9 @@ private:
const QQmlJSScope::ConstPtr &scope);
bool propagateTranslationMethod(const QList<QQmlJSMetaMethod> &methods, int argc, int argv);
void propagateStringArgCall(int argv);
- void propagatePropertyLookup(const QString &name);
+ bool propagateArrayMethod(const QString &name, int argc, int argv, const QQmlJSRegisterContent &valueType);
+ void propagatePropertyLookup(
+ const QString &name, int lookupIndex = QQmlJSRegisterContent::InvalidLookupIndex);
void propagateScopeLookupCall(const QString &functionName, int argc, int argv);
void saveRegisterStateForJump(int offset);
bool canConvertFromTo(const QQmlJSRegisterContent &from, const QQmlJSRegisterContent &to);
@@ -239,8 +243,22 @@ private:
void recordEqualsType(int lhs);
void recordCompareType(int lhs);
+ // helper functions to deal with special cases in generate_ methods
+ void generate_CallProperty_SCMath(int base, int arcg, int argv);
+ void generate_CallProperty_SCconsole(int base, int argc, int argv);
+ void generate_Construct_SCDate(int argc, int argv);
+ void generate_Construct_SCArray(int argc, int argv);
+
+ // helper functions to perform QQmlSA checks
+ void generate_ret_SAcheck();
+ void generate_LoadQmlContextPropertyLookup_SAcheck(const QString &name);
+ void generate_StoreNameCommon_SAcheck(const QQmlJSRegisterContent &in, const QString &name);
+ void propagatePropertyLookup_SAcheck(const QString &propertyName);
+ void generate_StoreProperty_SAcheck(const QString propertyName, const QQmlJSRegisterContent &callBase);
+ void generate_callProperty_SAcheck(const QString propertyName, const QQmlJSScope::ConstPtr &baseType);
+
+
QQmlJSRegisterContent m_returnType;
- QQmlJSTypeInfo *m_typeInfo = nullptr;
QQmlSA::PassManager *m_passManager = nullptr;
QQmlJSScope::ConstPtr m_attachedContext;
diff --git a/src/qmlcompiler/qqmljstypereader.cpp b/src/qmlcompiler/qqmljstypereader.cpp
index 86d547d714..6e2e119e42 100644
--- a/src/qmlcompiler/qqmljstypereader.cpp
+++ b/src/qmlcompiler/qqmljstypereader.cpp
@@ -44,23 +44,21 @@ bool QQmlJSTypeReader::operator ()(const QSharedPointer<QQmlJSScope> &scope)
const bool success = isJavaScript ? (isESModule ? parser.parseModule()
: parser.parseProgram())
: parser.parse();
- if (!success)
- return false;
QQmlJS::AST::Node *rootNode = parser.rootNode();
- if (!rootNode)
- return false;
QQmlJSLogger logger;
logger.setFileName(m_file);
logger.setCode(code);
logger.setSilent(true);
- auto membersVisitor = m_importer->makeImportVisitor(
- scope, m_importer, &logger,
- QQmlJSImportVisitor::implicitImportDirectory(m_file, m_importer->resourceFileMapper()));
- rootNode->accept(membersVisitor.get());
- return true;
+ m_importer->runImportVisitor(rootNode,
+ { scope,
+ &logger,
+ QQmlJSImportVisitor::implicitImportDirectory(
+ m_file, m_importer->resourceFileMapper()),
+ {} });
+ return success && rootNode;
}
QT_END_NAMESPACE
diff --git a/src/qmlcompiler/qqmljstyperesolver.cpp b/src/qmlcompiler/qqmljstyperesolver.cpp
index 901eccf387..491c87388d 100644
--- a/src/qmlcompiler/qqmljstyperesolver.cpp
+++ b/src/qmlcompiler/qqmljstyperesolver.cpp
@@ -19,42 +19,124 @@ using namespace Qt::StringLiterals;
Q_LOGGING_CATEGORY(lcTypeResolver, "qt.qml.compiler.typeresolver", QtInfoMsg);
+static inline void assertExtension(const QQmlJSScope::ConstPtr &type, QLatin1String extension)
+{
+ Q_ASSERT(type);
+ Q_ASSERT(type->extensionType().scope->internalName() == extension);
+ Q_ASSERT(type->extensionIsJavaScript());
+}
+
QQmlJSTypeResolver::QQmlJSTypeResolver(QQmlJSImporter *importer)
- : m_imports(importer->builtinInternalNames())
- , m_trackedTypes(std::make_unique<QHash<QQmlJSScope::ConstPtr, TrackedType>>())
+ : m_imports(importer->builtinInternalNames()),
+ m_trackedTypes(std::make_unique<QHash<QQmlJSScope::ConstPtr, TrackedType>>())
{
const QQmlJSImporter::ImportedTypes &builtinTypes = m_imports;
+
m_voidType = builtinTypes.type(u"void"_s).scope;
+ assertExtension(m_voidType, "undefined"_L1);
+
m_nullType = builtinTypes.type(u"std::nullptr_t"_s).scope;
+ Q_ASSERT(m_nullType);
+
m_realType = builtinTypes.type(u"double"_s).scope;
+ assertExtension(m_realType, "Number"_L1);
+
m_floatType = builtinTypes.type(u"float"_s).scope;
- m_intType = builtinTypes.type(u"int"_s).scope;
- m_uintType = builtinTypes.type(u"uint"_s).scope;
+ assertExtension(m_floatType, "Number"_L1);
+
+ m_int8Type = builtinTypes.type(u"qint8"_s).scope;
+ assertExtension(m_int8Type, "Number"_L1);
+
+ m_uint8Type = builtinTypes.type(u"quint8"_s).scope;
+ assertExtension(m_uint8Type, "Number"_L1);
+
+ m_int16Type = builtinTypes.type(u"short"_s).scope;
+ assertExtension(m_int16Type, "Number"_L1);
+
+ m_uint16Type = builtinTypes.type(u"ushort"_s).scope;
+ assertExtension(m_uint16Type, "Number"_L1);
+
+ m_int32Type = builtinTypes.type(u"int"_s).scope;
+ assertExtension(m_int32Type, "Number"_L1);
+
+ m_uint32Type = builtinTypes.type(u"uint"_s).scope;
+ assertExtension(m_uint32Type, "Number"_L1);
+
+ m_int64Type = builtinTypes.type(u"qlonglong"_s).scope;
+ Q_ASSERT(m_int64Type);
+
+ m_uint64Type = builtinTypes.type(u"qulonglong"_s).scope;
+ Q_ASSERT(m_uint64Type);
+
+ m_sizeType = builtinTypes.type(u"qsizetype"_s).scope;
+ assertExtension(m_sizeType, "Number"_L1);
+
+ // qsizetype is either a 32bit or a 64bit signed integer. We don't want to special-case it.
+ Q_ASSERT(m_sizeType == m_int32Type || m_sizeType == m_int64Type);
+
m_boolType = builtinTypes.type(u"bool"_s).scope;
+ assertExtension(m_boolType, "Boolean"_L1);
+
m_stringType = builtinTypes.type(u"QString"_s).scope;
+ assertExtension(m_stringType, "String"_L1);
+
m_stringListType = builtinTypes.type(u"QStringList"_s).scope;
+ assertExtension(m_stringListType, "Array"_L1);
+
m_byteArrayType = builtinTypes.type(u"QByteArray"_s).scope;
+ assertExtension(m_byteArrayType, "ArrayBuffer"_L1);
+
m_urlType = builtinTypes.type(u"QUrl"_s).scope;
+ assertExtension(m_urlType, "URL"_L1);
+
m_dateTimeType = builtinTypes.type(u"QDateTime"_s).scope;
+ assertExtension(m_dateTimeType, "Date"_L1);
+
m_dateType = builtinTypes.type(u"QDate"_s).scope;
+ Q_ASSERT(m_dateType);
+
m_timeType = builtinTypes.type(u"QTime"_s).scope;
+ Q_ASSERT(m_timeType);
+
m_variantListType = builtinTypes.type(u"QVariantList"_s).scope;
+ assertExtension(m_variantListType, "Array"_L1);
+
+ m_variantMapType = builtinTypes.type(u"QVariantMap"_s).scope;
+ Q_ASSERT(m_variantMapType);
m_varType = builtinTypes.type(u"QVariant"_s).scope;
+ Q_ASSERT(m_varType);
+
m_jsValueType = builtinTypes.type(u"QJSValue"_s).scope;
- m_listPropertyType = builtinTypes.type(u"QQmlListProperty<QObject>"_s).scope;
+ Q_ASSERT(m_jsValueType);
+
+ m_qObjectType = builtinTypes.type(u"QObject"_s).scope;
+ assertExtension(m_qObjectType, "Object"_L1);
+
m_qObjectListType = builtinTypes.type(u"QObjectList"_s).scope;
+ assertExtension(m_qObjectListType, "Array"_L1);
+
+ m_qQmlScriptStringType = builtinTypes.type(u"QQmlScriptString"_s).scope;
+ Q_ASSERT(m_qQmlScriptStringType);
+
+ m_functionType = builtinTypes.type(u"function"_s).scope;
+ Q_ASSERT(m_functionType);
+
+ m_numberPrototype = builtinTypes.type(u"NumberPrototype"_s).scope;
+ Q_ASSERT(m_numberPrototype);
+
+ m_arrayPrototype = builtinTypes.type(u"ArrayPrototype"_s).scope;
+ Q_ASSERT(m_arrayPrototype);
+
+ m_listPropertyType = m_qObjectType->listType();
+ Q_ASSERT(m_listPropertyType->internalName() == u"QQmlListProperty<QObject>"_s);
+ Q_ASSERT(m_listPropertyType->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence);
+ Q_ASSERT(m_listPropertyType->valueTypeName() == u"QObject"_s);
+ assertExtension(m_listPropertyType, "Array"_L1);
QQmlJSScope::Ptr emptyType = QQmlJSScope::create();
emptyType->setAccessSemantics(QQmlJSScope::AccessSemantics::None);
m_emptyType = emptyType;
- QQmlJSScope::Ptr emptyListType = QQmlJSScope::create();
- emptyListType->setInternalName(u"void*"_s);
- emptyListType->setAccessSemantics(QQmlJSScope::AccessSemantics::Sequence);
- QQmlJSScope::resolveTypes(emptyListType, builtinTypes);
- Q_ASSERT(!emptyListType->extensionType().scope.isNull());
- m_emptyListType = emptyListType;
-
QQmlJSScope::Ptr jsPrimitiveType = QQmlJSScope::create();
jsPrimitiveType->setInternalName(u"QJSPrimitiveValue"_s);
jsPrimitiveType->setFilePath(u"qjsprimitivevalue.h"_s);
@@ -67,29 +149,26 @@ QQmlJSTypeResolver::QQmlJSTypeResolver(QQmlJSImporter *importer)
metaObjectType->setAccessSemantics(QQmlJSScope::AccessSemantics::Reference);
m_metaObjectType = metaObjectType;
- QQmlJSScope::Ptr functionType = QQmlJSScope::create();
- functionType->setInternalName(u"function"_s);
- functionType->setAccessSemantics(QQmlJSScope::AccessSemantics::Value);
- m_functionType = functionType;
-
m_jsGlobalObject = importer->jsGlobalObject();
- auto numberMethods = m_jsGlobalObject->methods(u"Number"_s);
- Q_ASSERT(numberMethods.size() == 1);
- m_numberPrototype = numberMethods[0].returnType()->baseType();
- Q_ASSERT(m_numberPrototype);
- Q_ASSERT(m_numberPrototype->internalName() == u"NumberPrototype"_s);
- auto arrayMethods = m_jsGlobalObject->methods(u"Array"_s);
- Q_ASSERT(arrayMethods.size() == 1);
- m_arrayType = arrayMethods[0].returnType();
- Q_ASSERT(m_arrayType);
+ QQmlJSScope::Ptr forInIteratorPtr = QQmlJSScope::create();
+ forInIteratorPtr->setAccessSemantics(QQmlJSScope::AccessSemantics::Value);
+ forInIteratorPtr->setFilePath(u"qjslist.h"_s);
+ forInIteratorPtr->setInternalName(u"QJSListForInIterator::Ptr"_s);
+ m_forInIteratorPtr = forInIteratorPtr;
+
+ QQmlJSScope::Ptr forOfIteratorPtr = QQmlJSScope::create();
+ forOfIteratorPtr->setAccessSemantics(QQmlJSScope::AccessSemantics::Value);
+ forOfIteratorPtr->setFilePath(u"qjslist.h"_s);
+ forOfIteratorPtr->setInternalName(u"QJSListForOfIterator::Ptr"_s);
+ m_forOfIteratorPtr = forOfIteratorPtr;
}
/*!
\internal
Initializes the type resolver. As part of that initialization, makes \a
- visitor traverse the program.
+ visitor traverse the program when given.
*/
void QQmlJSTypeResolver::init(QQmlJSImportVisitor *visitor, QQmlJS::AST::Node *program)
{
@@ -100,7 +179,8 @@ void QQmlJSTypeResolver::init(QQmlJSImportVisitor *visitor, QQmlJS::AST::Node *p
m_imports.clearTypes();
m_signalHandlers.clear();
- program->accept(visitor);
+ if (program)
+ program->accept(visitor);
m_objectsById = visitor->addressableScopes();
m_objectsByLocation = visitor->scopesBylocation();
@@ -108,6 +188,16 @@ void QQmlJSTypeResolver::init(QQmlJSImportVisitor *visitor, QQmlJS::AST::Node *p
m_imports = visitor->imports();
}
+QQmlJSScope::ConstPtr QQmlJSTypeResolver::mathObject() const
+{
+ return jsGlobalObject()->property(u"Math"_s).type();
+}
+
+QQmlJSScope::ConstPtr QQmlJSTypeResolver::consoleObject() const
+{
+ return jsGlobalObject()->property(u"console"_s).type();
+}
+
QQmlJSScope::ConstPtr
QQmlJSTypeResolver::scopeForLocation(const QV4::CompiledData::Location &location) const
{
@@ -117,19 +207,15 @@ QQmlJSTypeResolver::scopeForLocation(const QV4::CompiledData::Location &location
return m_objectsByLocation[location];
}
-QQmlJSScope::ConstPtr QQmlJSTypeResolver::scopeForId(
- const QString &id, const QQmlJSScope::ConstPtr &referrer) const
-{
- return m_objectsById.scope(id, referrer);
-}
-
QQmlJSScope::ConstPtr QQmlJSTypeResolver::typeFromAST(QQmlJS::AST::Type *type) const
{
const QString typeId = QmlIR::IRBuilder::asString(type->typeId);
if (!type->typeArgument)
return m_imports.type(typeId).scope;
- if (typeId == u"list"_s)
- return typeForName(type->typeArgument->toString())->listType();
+ if (typeId == u"list"_s) {
+ if (const QQmlJSScope::ConstPtr typeArgument = typeForName(type->typeArgument->toString()))
+ return typeArgument->listType();
+ }
return QQmlJSScope::ConstPtr();
}
@@ -140,7 +226,7 @@ QQmlJSScope::ConstPtr QQmlJSTypeResolver::typeForConst(QV4::ReturnedValue rv) co
return voidType();
if (value.isInt32())
- return intType();
+ return int32Type();
if (value.isBoolean())
return boolType();
@@ -180,18 +266,18 @@ QQmlJSTypeResolver::typeForBinaryOperation(QSOperator::Op oper, const QQmlJSRegi
case QSOperator::Op::BitXor:
case QSOperator::Op::LShift:
case QSOperator::Op::RShift:
- return builtinType(intType());
+ return builtinType(int32Type());
case QSOperator::Op::URShift:
- return builtinType(uintType());
+ return builtinType(uint32Type());
case QSOperator::Op::Add: {
- const auto leftContents = containedType(left);
- const auto rightContents = containedType(right);
+ const auto leftContents = left.containedType();
+ const auto rightContents = right.containedType();
if (equals(leftContents, stringType()) || equals(rightContents, stringType()))
return builtinType(stringType());
const QQmlJSScope::ConstPtr result = merge(leftContents, rightContents);
if (equals(result, boolType()))
- return builtinType(intType());
+ return builtinType(int32Type());
if (isNumeric(result))
return builtinType(realType());
@@ -200,8 +286,8 @@ QQmlJSTypeResolver::typeForBinaryOperation(QSOperator::Op oper, const QQmlJSRegi
case QSOperator::Op::Sub:
case QSOperator::Op::Mul:
case QSOperator::Op::Exp: {
- const QQmlJSScope::ConstPtr result = merge(containedType(left), containedType(right));
- return builtinType(equals(result, boolType()) ? intType() : realType());
+ const QQmlJSScope::ConstPtr result = merge(left.containedType(), right.containedType());
+ return builtinType(equals(result, boolType()) ? int32Type() : realType());
}
case QSOperator::Op::Div:
case QSOperator::Op::Mod:
@@ -222,14 +308,14 @@ QQmlJSRegisterContent QQmlJSTypeResolver::typeForArithmeticUnaryOperation(
case UnaryOperator::Not:
return builtinType(boolType());
case UnaryOperator::Complement:
- return builtinType(intType());
+ return builtinType(int32Type());
case UnaryOperator::Plus:
if (isIntegral(operand))
return operand;
Q_FALLTHROUGH();
default:
- if (equals(containedType(operand), boolType()))
- return builtinType(intType());
+ if (equals(operand.containedType(), boolType()))
+ return builtinType(int32Type());
break;
}
@@ -238,23 +324,28 @@ QQmlJSRegisterContent QQmlJSTypeResolver::typeForArithmeticUnaryOperation(
bool QQmlJSTypeResolver::isPrimitive(const QQmlJSRegisterContent &type) const
{
- return isPrimitive(containedType(type));
+ return isPrimitive(type.containedType());
}
bool QQmlJSTypeResolver::isNumeric(const QQmlJSRegisterContent &type) const
{
- return isNumeric(containedType(type));
+ return isNumeric(type.containedType());
}
bool QQmlJSTypeResolver::isIntegral(const QQmlJSRegisterContent &type) const
{
- return equals(containedType(type), m_intType) || equals(containedType(type), m_uintType);
+ return isIntegral(type.containedType());
+}
+
+bool QQmlJSTypeResolver::isIntegral(const QQmlJSScope::ConstPtr &type) const
+{
+ // Only types of length <= 32bit count as integral
+ return isSignedInteger(type) || isUnsignedInteger(type);
}
bool QQmlJSTypeResolver::isPrimitive(const QQmlJSScope::ConstPtr &type) const
{
- return equals(type, m_intType) || equals(type, m_uintType)
- || equals(type, m_realType) || equals(type, m_floatType)
+ return isNumeric(type)
|| equals(type, m_boolType) || equals(type, m_voidType) || equals(type, m_nullType)
|| equals(type, m_stringType) || equals(type, m_jsPrimitiveType);
}
@@ -266,34 +357,33 @@ bool QQmlJSTypeResolver::isNumeric(const QQmlJSScope::ConstPtr &type) const
if (mode == QQmlJSScope::ExtensionNamespace)
return false;
return equals(scope, m_numberPrototype);
- });
+ });
}
-QQmlJSScope::ConstPtr
-QQmlJSTypeResolver::containedType(const QQmlJSRegisterContent &container) const
-{
- if (container.isType())
- return container.type();
- if (container.isProperty())
- return container.property().type();
- if (container.isEnumeration())
- return container.enumeration().type();
- if (container.isMethod())
- return container.storedType(); // Methods can only be stored in QJSValue.
- if (container.isImportNamespace()) {
- switch (container.variant()) {
- case QQmlJSRegisterContent::ScopeModulePrefix:
- return container.storedType(); // We don't store scope module prefixes
- case QQmlJSRegisterContent::ObjectModulePrefix:
- return container.scopeType(); // We need to pass the original object through.
- default:
- Q_UNREACHABLE();
- }
- }
- if (container.isConversion())
- return container.conversionResult();
+bool QQmlJSTypeResolver::isSignedInteger(const QQmlJSScope::ConstPtr &type) const
+{
+ return equals(type, m_int8Type)
+ || equals(type, m_int16Type)
+ || equals(type, m_int32Type)
+ || equals(type, m_int64Type);
+}
- Q_UNREACHABLE_RETURN({});
+bool QQmlJSTypeResolver::isUnsignedInteger(const QQmlJSScope::ConstPtr &type) const
+{
+ return equals(type, m_uint8Type)
+ || equals(type, m_uint16Type)
+ || equals(type, m_uint32Type)
+ || equals(type, m_uint64Type);
+}
+
+bool QQmlJSTypeResolver::isNativeArrayIndex(const QQmlJSScope::ConstPtr &type) const
+{
+ return (equals(type, m_uint8Type)
+ || equals(type, m_int8Type)
+ || equals(type, m_uint16Type)
+ || equals(type, m_int16Type)
+ || equals(type, m_uint32Type)
+ || equals(type, m_int32Type));
}
QQmlJSScope::ConstPtr QQmlJSTypeResolver::trackedType(const QQmlJSScope::ConstPtr &type) const
@@ -316,49 +406,85 @@ QQmlJSRegisterContent QQmlJSTypeResolver::transformed(
{
if (origin.isType()) {
return QQmlJSRegisterContent::create(
- (this->*op)(origin.storedType()), (this->*op)(origin.type()),
- origin.variant(), (this->*op)(origin.scopeType()));
+ (this->*op)(origin.type()), origin.resultLookupIndex(), origin.variant(),
+ (this->*op)(origin.scopeType()));
}
if (origin.isProperty()) {
QQmlJSMetaProperty prop = origin.property();
prop.setType((this->*op)(prop.type()));
return QQmlJSRegisterContent::create(
- (this->*op)(origin.storedType()), prop,
- origin.variant(), (this->*op)(origin.scopeType()));
+ prop, origin.baseLookupIndex(), origin.resultLookupIndex(), origin.variant(),
+ (this->*op)(origin.scopeType()));
}
if (origin.isEnumeration()) {
QQmlJSMetaEnum enumeration = origin.enumeration();
enumeration.setType((this->*op)(enumeration.type()));
return QQmlJSRegisterContent::create(
- (this->*op)(origin.storedType()), enumeration, origin.enumMember(),
- origin.variant(), (this->*op)(origin.scopeType()));
+ enumeration, origin.enumMember(), origin.variant(),
+ (this->*op)(origin.scopeType()));
}
if (origin.isMethod()) {
return QQmlJSRegisterContent::create(
- (this->*op)(origin.storedType()), origin.method(), origin.variant(),
+ origin.method(), (this->*op)(origin.methodType()), origin.variant(),
(this->*op)(origin.scopeType()));
}
if (origin.isImportNamespace()) {
return QQmlJSRegisterContent::create(
- (this->*op)(origin.storedType()), origin.importNamespace(),
+ origin.importNamespace(), (this->*op)(origin.importNamespaceType()),
origin.variant(), (this->*op)(origin.scopeType()));
}
if (origin.isConversion()) {
+ // When retrieving the originals we want a deep retrieval.
+ // When tracking a new type, we don't want to re-track its originals, though.
+
+ const QList<QQmlJSScope::ConstPtr> origins = origin.conversionOrigins();
+ QList<QQmlJSScope::ConstPtr> transformedOrigins;
+ if (op == &QQmlJSTypeResolver::trackedType) {
+ transformedOrigins = origins;
+ } else {
+ transformedOrigins.reserve(origins.length());
+ for (const QQmlJSScope::ConstPtr &origin: origins)
+ transformedOrigins.append((this->*op)(origin));
+ }
+
return QQmlJSRegisterContent::create(
- (this->*op)(origin.storedType()), origin.conversionOrigins(),
+ transformedOrigins,
(this->*op)(origin.conversionResult()),
+ (this->*op)(origin.conversionResultScope()),
origin.variant(), (this->*op)(origin.scopeType()));
}
Q_UNREACHABLE_RETURN({});
}
-QQmlJSRegisterContent QQmlJSTypeResolver::referenceTypeForName(
+QQmlJSScope::ConstPtr QQmlJSTypeResolver::containedTypeForName(const QString &name) const
+{
+ QQmlJSScope::ConstPtr type = typeForName(name);
+
+ if (!type || type->isSingleton() || type->isScript())
+ return type;
+
+ switch (type->accessSemantics()) {
+ case QQmlJSScope::AccessSemantics::Reference:
+ if (const auto attached = type->attachedType())
+ return genericType(attached) ? attached : QQmlJSScope::ConstPtr();
+ return metaObjectType();
+ case QQmlJSScope::AccessSemantics::None:
+ return metaObjectType();
+ case QQmlJSScope::AccessSemantics::Sequence:
+ case QQmlJSScope::AccessSemantics::Value:
+ return canAddressValueTypes() ? metaObjectType() : QQmlJSScope::ConstPtr();
+ }
+
+ Q_UNREACHABLE_RETURN(QQmlJSScope::ConstPtr());
+}
+
+QQmlJSRegisterContent QQmlJSTypeResolver::registerContentForName(
const QString &name, const QQmlJSScope::ConstPtr &scopeType,
bool hasObjectModulePrefix) const
{
@@ -366,13 +492,17 @@ QQmlJSRegisterContent QQmlJSTypeResolver::referenceTypeForName(
if (!type)
return QQmlJSRegisterContent();
- if (type->isSingleton())
- return QQmlJSRegisterContent::create(storedType(type), type,
- QQmlJSRegisterContent::Singleton, scopeType);
+ if (type->isSingleton()) {
+ return QQmlJSRegisterContent::create(
+ type, QQmlJSRegisterContent::InvalidLookupIndex,
+ QQmlJSRegisterContent::Singleton, scopeType);
+ }
- if (type->isScript())
- return QQmlJSRegisterContent::create(storedType(type), type,
- QQmlJSRegisterContent::Script, scopeType);
+ if (type->isScript()) {
+ return QQmlJSRegisterContent::create(
+ type, QQmlJSRegisterContent::InvalidLookupIndex,
+ QQmlJSRegisterContent::Script, scopeType);
+ }
if (const auto attached = type->attachedType()) {
if (!genericType(attached)) {
@@ -390,7 +520,7 @@ QQmlJSRegisterContent QQmlJSTypeResolver::referenceTypeForName(
// mode, we will figure this out using the scope type and access any enums of the
// plain type directly. In indirect mode, we can use enum lookups.
return QQmlJSRegisterContent::create(
- storedType(attached), attached,
+ attached, QQmlJSRegisterContent::InvalidLookupIndex,
hasObjectModulePrefix
? QQmlJSRegisterContent::ObjectAttached
: QQmlJSRegisterContent::ScopeAttached, type);
@@ -404,11 +534,17 @@ QQmlJSRegisterContent QQmlJSTypeResolver::referenceTypeForName(
// We may still need the plain type reference for enum lookups,
// Store it as QMetaObject.
// This only works with namespaces and object types.
- return QQmlJSRegisterContent::create(metaObjectType(), metaObjectType(),
- QQmlJSRegisterContent::MetaType, type);
+ return QQmlJSRegisterContent::create(
+ metaObjectType(), QQmlJSRegisterContent::InvalidLookupIndex,
+ QQmlJSRegisterContent::MetaType, type);
case QQmlJSScope::AccessSemantics::Sequence:
case QQmlJSScope::AccessSemantics::Value:
- // This is not actually a type reference. You cannot get the metaobject
+ if (canAddressValueTypes()) {
+ return QQmlJSRegisterContent::create(
+ metaObjectType(), QQmlJSRegisterContent::InvalidLookupIndex,
+ QQmlJSRegisterContent::MetaType, type);
+ }
+ // Else this is not actually a type reference. You cannot get the metaobject
// of a value type in QML and sequences don't even have metaobjects.
break;
}
@@ -429,33 +565,43 @@ QQmlJSRegisterContent QQmlJSTypeResolver::tracked(const QQmlJSRegisterContent &t
QQmlJSScope::ConstPtr QQmlJSTypeResolver::trackedContainedType(
const QQmlJSRegisterContent &container) const
{
- const QQmlJSScope::ConstPtr type = containedType(container);
+ const QQmlJSScope::ConstPtr type = container.containedType();
return m_trackedTypes->contains(type) ? type : QQmlJSScope::ConstPtr();
}
QQmlJSScope::ConstPtr QQmlJSTypeResolver::originalContainedType(
const QQmlJSRegisterContent &container) const
{
- return originalType(containedType(container));
+ return originalType(container.containedType());
}
-void QQmlJSTypeResolver::adjustTrackedType(
+bool QQmlJSTypeResolver::adjustTrackedType(
const QQmlJSScope::ConstPtr &tracked, const QQmlJSScope::ConstPtr &conversion) const
{
if (m_cloneMode == QQmlJSTypeResolver::DoNotCloneTypes)
- return;
+ return true;
const auto it = m_trackedTypes->find(tracked);
Q_ASSERT(it != m_trackedTypes->end());
+
+ // If we cannot convert to the new type without the help of e.g. lookupResultMetaType(),
+ // we better not change the type.
+ if (!canPrimitivelyConvertFromTo(tracked, conversion)
+ && !canPopulate(conversion, tracked, nullptr)
+ && !selectConstructor(conversion, tracked, nullptr).isValid()) {
+ return false;
+ }
+
it->replacement = comparableType(conversion);
*it->clone = std::move(*QQmlJSScope::clone(conversion));
+ return true;
}
-void QQmlJSTypeResolver::adjustTrackedType(
+bool QQmlJSTypeResolver::adjustTrackedType(
const QQmlJSScope::ConstPtr &tracked, const QList<QQmlJSScope::ConstPtr> &conversions) const
{
if (m_cloneMode == QQmlJSTypeResolver::DoNotCloneTypes)
- return;
+ return true;
const auto it = m_trackedTypes->find(tracked);
Q_ASSERT(it != m_trackedTypes->end());
@@ -466,10 +612,28 @@ void QQmlJSTypeResolver::adjustTrackedType(
// If we cannot convert to the new type without the help of e.g. lookupResultMetaType(),
// we better not change the type.
- if (canPrimitivelyConvertFromTo(tracked, result)) {
- it->replacement = comparableType(result);
- *mutableTracked = std::move(*QQmlJSScope::clone(result));
+ if (!canPrimitivelyConvertFromTo(tracked, result)
+ && !canPopulate(result, tracked, nullptr)
+ && !selectConstructor(result, tracked, nullptr).isValid()) {
+ return false;
}
+
+ it->replacement = comparableType(result);
+ *mutableTracked = std::move(*QQmlJSScope::clone(result));
+ return true;
+}
+
+void QQmlJSTypeResolver::adjustOriginalType(
+ const QQmlJSScope::ConstPtr &tracked, const QQmlJSScope::ConstPtr &conversion) const
+{
+ if (m_cloneMode == QQmlJSTypeResolver::DoNotCloneTypes)
+ return;
+
+ const auto it = m_trackedTypes->find(tracked);
+ Q_ASSERT(it != m_trackedTypes->end());
+
+ it->original = conversion;
+ *it->clone = std::move(*QQmlJSScope::clone(conversion));
}
void QQmlJSTypeResolver::generalizeType(const QQmlJSScope::ConstPtr &type) const
@@ -485,35 +649,14 @@ void QQmlJSTypeResolver::generalizeType(const QQmlJSScope::ConstPtr &type) const
it->original = genericType(it->original);
}
-QString QQmlJSTypeResolver::containedTypeName(const QQmlJSRegisterContent &container,
- bool useFancyName) const
-{
- QQmlJSScope::ConstPtr type;
-
- // Use the type proper instead of the attached type
- switch (container.variant()) {
- case QQmlJSRegisterContent::ScopeAttached:
- case QQmlJSRegisterContent::MetaType:
- type = container.scopeType();
- break;
- default:
- type = containedType(container);
- break;
- }
-
- QString typeName = type->internalName().isEmpty() ? type->baseTypeName() : type->internalName();
-
- if (useFancyName)
- return QQmlJSScope::prettyName(typeName);
-
- return typeName;
-}
-
bool QQmlJSTypeResolver::canConvertFromTo(const QQmlJSScope::ConstPtr &from,
const QQmlJSScope::ConstPtr &to) const
{
- if (canPrimitivelyConvertFromTo(from, to))
+ if (canPrimitivelyConvertFromTo(from, to)
+ || canPopulate(to, from, nullptr)
+ || selectConstructor(to, from, nullptr).isValid()) {
return true;
+ }
// ### need a generic solution for custom cpp types:
// if (from->m_hasBoolOverload && equals(to, boolType))
@@ -525,11 +668,9 @@ bool QQmlJSTypeResolver::canConvertFromTo(const QQmlJSScope::ConstPtr &from,
// in QQmlJSCodeGenerator::conversion().
if (equals(from, m_stringType) && !to.isNull()) {
const QString toTypeName = to->internalName();
- if (toTypeName == u"QTime"_s || toTypeName == u"QDate"_s
- || toTypeName == u"QPoint"_s || toTypeName == u"QPointF"_s
+ if (toTypeName == u"QPoint"_s || toTypeName == u"QPointF"_s
|| toTypeName == u"QSize"_s || toTypeName == u"QSizeF"_s
- || toTypeName == u"QRect"_s || toTypeName == u"QRectF"_s
- || toTypeName == u"QColor"_s) {
+ || toTypeName == u"QRect"_s || toTypeName == u"QRectF"_s) {
return true;
}
}
@@ -540,7 +681,7 @@ bool QQmlJSTypeResolver::canConvertFromTo(const QQmlJSScope::ConstPtr &from,
bool QQmlJSTypeResolver::canConvertFromTo(const QQmlJSRegisterContent &from,
const QQmlJSRegisterContent &to) const
{
- return canConvertFromTo(containedType(from), containedType(to));
+ return canConvertFromTo(from.containedType(), to.containedType());
}
static QQmlJSRegisterContent::ContentVariant mergeVariants(QQmlJSRegisterContent::ContentVariant a,
@@ -552,25 +693,37 @@ static QQmlJSRegisterContent::ContentVariant mergeVariants(QQmlJSRegisterContent
QQmlJSRegisterContent QQmlJSTypeResolver::merge(const QQmlJSRegisterContent &a,
const QQmlJSRegisterContent &b) const
{
+ if (a == b)
+ return a;
+
QList<QQmlJSScope::ConstPtr> origins;
- if (a.isConversion())
+
+ QQmlJSScope::ConstPtr aResultScope;
+ if (a.isConversion()) {
origins.append(a.conversionOrigins());
- else
- origins.append(containedType(a));
+ aResultScope = a.conversionResultScope();
+ } else {
+ origins.append(a.containedType());
+ aResultScope = a.scopeType();
+ }
- if (b.isConversion())
+ QQmlJSScope::ConstPtr bResultScope;
+ if (b.isConversion()) {
origins.append(b.conversionOrigins());
- else
- origins.append(containedType(b));
+ bResultScope = b.conversionResultScope();
+ } else {
+ origins.append(b.containedType());
+ bResultScope = b.scopeType();
+ }
std::sort(origins.begin(), origins.end());
const auto erase = std::unique(origins.begin(), origins.end());
origins.erase(erase, origins.end());
return QQmlJSRegisterContent::create(
- merge(a.storedType(), b.storedType()),
origins,
- merge(containedType(a), containedType(b)),
+ merge(a.containedType(), b.containedType()),
+ merge(aResultScope, bResultScope),
mergeVariants(a.variant(), b.variant()),
merge(a.scopeType(), b.scopeType()));
}
@@ -605,36 +758,81 @@ QQmlJSScope::ConstPtr QQmlJSTypeResolver::merge(const QQmlJSScope::ConstPtr &a,
if (equals(b, jsValueType()) || equals(b, varType()))
return b;
- auto canConvert = [&](const QQmlJSScope::ConstPtr &from, const QQmlJSScope::ConstPtr &to) {
- return (equals(a, from) && equals(b, to)) || (equals(b, from) && equals(a, to));
+ const auto isInt32Compatible = [&](const QQmlJSScope::ConstPtr &type) {
+ return (isIntegral(type) && !equals(type, uint32Type())) || equals(type, boolType());
};
+ if (isInt32Compatible(a) && isInt32Compatible(b))
+ return int32Type();
+
+ const auto isUInt32Compatible = [&](const QQmlJSScope::ConstPtr &type) {
+ return isUnsignedInteger(type) || equals(type, boolType());
+ };
+
+ if (isUInt32Compatible(a) && isUInt32Compatible(b))
+ return uint32Type();
+
if (isNumeric(a) && isNumeric(b))
return realType();
- if (canConvert(boolType(), intType()))
- return intType();
- if (canConvert(boolType(), uintType()))
- return uintType();
- if (canConvert(intType(), stringType()))
- return stringType();
- if (canConvert(uintType(), stringType()))
- return stringType();
if (isPrimitive(a) && isPrimitive(b))
return jsPrimitiveType();
if (auto commonBase = commonBaseType(a, b))
return commonBase;
- if (equals(a, nullType()) && b->accessSemantics() == QQmlJSScope::AccessSemantics::Reference)
+ if ((equals(a, nullType()) || equals(a, boolType())) && b->isReferenceType())
return b;
- if (equals(b, nullType()) && a->accessSemantics() == QQmlJSScope::AccessSemantics::Reference)
+ if ((equals(b, nullType()) || equals(b, boolType())) && a->isReferenceType())
return a;
return varType();
}
+bool QQmlJSTypeResolver::canHold(
+ const QQmlJSScope::ConstPtr &container, const QQmlJSScope::ConstPtr &contained) const
+{
+ if (equals(container, contained)
+ || equals(container, m_varType)
+ || equals(container, m_jsValueType)) {
+ return true;
+ }
+
+ if (equals(container, m_jsPrimitiveType))
+ return isPrimitive(contained);
+
+ if (equals(container, m_variantListType))
+ return contained->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence;
+
+ if (equals(container, m_qObjectListType) || equals(container, m_listPropertyType)) {
+ if (contained->accessSemantics() != QQmlJSScope::AccessSemantics::Sequence)
+ return false;
+ if (QQmlJSScope::ConstPtr value = contained->valueType())
+ return value->isReferenceType();
+ return false;
+ }
+
+ if (QQmlJSUtils::searchBaseAndExtensionTypes(
+ container, [&](const QQmlJSScope::ConstPtr &base) {
+ return equals(base, contained);
+ })) {
+ return true;
+ }
+
+ if (container->isReferenceType()) {
+ if (QQmlJSUtils::searchBaseAndExtensionTypes(
+ contained, [&](const QQmlJSScope::ConstPtr &base) {
+ return equals(base, container);
+ })) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
bool QQmlJSTypeResolver::canHoldUndefined(const QQmlJSRegisterContent &content) const
{
const auto canBeUndefined = [this](const QQmlJSScope::ConstPtr &type) {
@@ -642,11 +840,11 @@ bool QQmlJSTypeResolver::canHoldUndefined(const QQmlJSRegisterContent &content)
|| equals(type, m_jsValueType) || equals(type, m_jsPrimitiveType);
};
- if (!canBeUndefined(content.storedType()))
+ if (!canBeUndefined(content.containedType()))
return false;
if (!content.isConversion())
- return canBeUndefined(containedType(content));
+ return true;
const auto origins = content.conversionOrigins();
for (const auto &origin : origins) {
@@ -657,6 +855,30 @@ bool QQmlJSTypeResolver::canHoldUndefined(const QQmlJSRegisterContent &content)
return false;
}
+bool QQmlJSTypeResolver::isOptionalType(const QQmlJSRegisterContent &content) const
+{
+ if (!content.isConversion())
+ return false;
+
+ const auto origins = content.conversionOrigins();
+ if (origins.length() != 2)
+ return false;
+
+ return equals(origins[0], m_voidType) || equals(origins[1], m_voidType);
+}
+
+QQmlJSScope::ConstPtr QQmlJSTypeResolver::extractNonVoidFromOptionalType(
+ const QQmlJSRegisterContent &content) const
+{
+ if (!isOptionalType(content))
+ return QQmlJSScope::ConstPtr();
+
+ const auto origins = content.conversionOrigins();
+ const QQmlJSScope::ConstPtr result = equals(origins[0], m_voidType) ? origins[1] : origins[0];
+ Q_ASSERT(!equals(result, m_voidType));
+ return result;
+}
+
QQmlJSScope::ConstPtr QQmlJSTypeResolver::genericType(
const QQmlJSScope::ConstPtr &type,
ComponentIsGeneric allowComponent) const
@@ -704,33 +926,33 @@ QQmlJSScope::ConstPtr QQmlJSTypeResolver::genericType(
if (type->isListProperty())
return m_listPropertyType;
- if (isPrimitive(type) || equals(type, m_jsValueType)
- || equals(type, m_urlType) || equals(type, m_dateTimeType)
- || equals(type, m_dateType) || equals(type, m_timeType)
- || equals(type, m_variantListType) || equals(type, m_varType)
- || equals(type, m_stringListType) || equals(type, m_emptyListType)
- || equals(type, m_byteArrayType)) {
- return type;
- }
+ if (type->scopeType() == QQmlSA::ScopeType::EnumScope)
+ return type->baseType();
- if (type->scopeType() == QQmlJSScope::EnumScope)
- return m_intType;
+ if (isPrimitive(type)) {
+ // If the filePath is set, the type is storable and we can just return it.
+ if (!type->filePath().isEmpty())
+ return type;
- if (isNumeric(type))
- return m_realType;
+ // If the type is JavaScript's 'number' type, it's not directly storable, but still
+ // primitive. We use C++ 'double' then.
+ if (isNumeric(type))
+ return m_realType;
- if (type->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence) {
- if (const QQmlJSScope::ConstPtr valueType = type->valueType()) {
- switch (valueType->accessSemantics()) {
- case QQmlJSScope::AccessSemantics::Value:
- return genericType(valueType)->listType();
- case QQmlJSScope::AccessSemantics::Reference:
- return m_qObjectListType;
- default:
- break;
- }
- }
- return m_variantListType;
+ // Otherwise we use QJSPrimitiveValue.
+ // TODO: JavaScript's 'string' and 'boolean' could be special-cased here.
+ return m_jsPrimitiveType;
+ }
+
+ for (const QQmlJSScope::ConstPtr &builtin : {
+ m_realType, m_floatType, m_int8Type, m_uint8Type, m_int16Type, m_uint16Type,
+ m_int32Type, m_uint32Type, m_int64Type, m_uint64Type, m_boolType, m_stringType,
+ m_stringListType, m_byteArrayType, m_urlType, m_dateTimeType, m_dateType,
+ m_timeType, m_variantListType, m_variantMapType, m_varType, m_jsValueType,
+ m_jsPrimitiveType, m_listPropertyType, m_qObjectType, m_qObjectListType,
+ m_metaObjectType, m_forInIteratorPtr, m_forOfIteratorPtr }) {
+ if (equals(type, builtin) || equals(type, builtin->listType()))
+ return type;
}
return m_varType;
@@ -739,12 +961,15 @@ QQmlJSScope::ConstPtr QQmlJSTypeResolver::genericType(
QQmlJSRegisterContent QQmlJSTypeResolver::builtinType(const QQmlJSScope::ConstPtr &type) const
{
Q_ASSERT(storedType(type) == type);
- return QQmlJSRegisterContent::create(type, type, QQmlJSRegisterContent::Builtin);
+ return QQmlJSRegisterContent::create(
+ type, QQmlJSRegisterContent::InvalidLookupIndex, QQmlJSRegisterContent::Builtin);
}
QQmlJSRegisterContent QQmlJSTypeResolver::globalType(const QQmlJSScope::ConstPtr &type) const
{
- return QQmlJSRegisterContent::create(storedType(type), type, QQmlJSRegisterContent::Unknown);
+ return QQmlJSRegisterContent::create(
+ type, QQmlJSRegisterContent::InvalidLookupIndex,
+ QQmlJSRegisterContent::Unknown);
}
static QQmlJSRegisterContent::ContentVariant scopeContentVariant(QQmlJSScope::ExtensionKind mode,
@@ -754,6 +979,7 @@ static QQmlJSRegisterContent::ContentVariant scopeContentVariant(QQmlJSScope::Ex
case QQmlJSScope::NotExtension:
return isMethod ? QQmlJSRegisterContent::ScopeMethod : QQmlJSRegisterContent::ScopeProperty;
case QQmlJSScope::ExtensionType:
+ case QQmlJSScope::ExtensionJavaScript:
return isMethod ? QQmlJSRegisterContent::ExtensionScopeMethod
: QQmlJSRegisterContent::ExtensionScopeProperty;
case QQmlJSScope::ExtensionNamespace:
@@ -779,95 +1005,203 @@ static bool isRevisionAllowed(int memberRevision, const QQmlJSScope::ConstPtr &s
return typeRevision.isValid() && typeRevision >= revision;
}
-QQmlJSRegisterContent QQmlJSTypeResolver::scopedType(const QQmlJSScope::ConstPtr &scope,
- const QString &name) const
+QQmlJSScope::ConstPtr QQmlJSTypeResolver::resolveParentProperty(
+ const QString &name, const QQmlJSScope::ConstPtr &base,
+ const QQmlJSScope::ConstPtr &propType) const
{
- const auto isAssignedToDefaultProperty = [this](const QQmlJSScope::ConstPtr &parent,
- const QQmlJSScope::ConstPtr &child) {
- const QString defaultPropertyName = parent->defaultPropertyName();
- if (defaultPropertyName.isEmpty()) // no reason to search for bindings
- return false;
+ if (m_parentMode != UseDocumentParent || name != base->parentPropertyName())
+ return propType;
+
+ const QQmlJSScope::ConstPtr baseParent = base->parentScope();
+ if (!baseParent || !baseParent->inherits(propType))
+ return propType;
+
+ const QString defaultPropertyName = baseParent->defaultPropertyName();
+ if (defaultPropertyName.isEmpty()) // no reason to search for bindings
+ return propType;
+
+ const QList<QQmlJSMetaPropertyBinding> defaultPropBindings
+ = baseParent->propertyBindings(defaultPropertyName);
+ for (const QQmlJSMetaPropertyBinding &binding : defaultPropBindings) {
+ if (binding.bindingType() == QQmlSA::BindingType::Object
+ && equals(binding.objectType(), base)) {
+ return baseParent;
+ }
+ }
+
+ return propType;
+}
+
+/*!
+ * \internal
+ *
+ * Retrieves the type of whatever \a name signifies in the given \a scope.
+ * \a name can be an ID, a property of the scope, a singleton, an attachment,
+ * a plain type reference or a JavaScript global.
+ *
+ * TODO: The lookup is actually wrong. We cannot really retrieve JavaScript
+ * globals here because any runtime-inserted context property would
+ * override them. We still do because we don't have a better solution for
+ * identifying e.g. the console object, yet.
+ *
+ * \a options tells us whether to consider components as bound. If components
+ * are bound we can retrieve objects identified by ID in outer contexts.
+ *
+ * TODO: This is also wrong because we should alternate scopes and contexts when
+ * traveling the scope/context hierarchy. Currently we have IDs from any
+ * context override all scope properties if components are considered
+ * bound. This is mostly because we don't care about outer scopes at all;
+ * so we cannot determine with certainty whether an ID from a far outer
+ * context is overridden by a property of a near outer scope. To
+ * complicate this further, user context properties can also be inserted
+ * in outer contexts at run time, shadowing names in further removed outer
+ * scopes and contexts. What we need to do is determine where exactly what
+ * kind of property can show up and defend against that with additional
+ * pragmas.
+ *
+ * Note: It probably takes at least 3 nested bound components in one document to
+ * trigger the misbehavior.
+ */
+QQmlJSScope::ConstPtr QQmlJSTypeResolver::scopedType(
+ const QQmlJSScope::ConstPtr &scope, const QString &name,
+ QQmlJSScopesByIdOptions options) const
+{
+ if (QQmlJSScope::ConstPtr identified = m_objectsById.scope(name, scope, options))
+ return identified;
+
+ if (QQmlJSScope::ConstPtr base = QQmlJSScope::findCurrentQMLScope(scope)) {
+ QQmlJSScope::ConstPtr result;
+ if (QQmlJSUtils::searchBaseAndExtensionTypes(
+ base, [&](const QQmlJSScope::ConstPtr &found, QQmlJSScope::ExtensionKind mode) {
+ if (mode == QQmlJSScope::ExtensionNamespace) // no use for it here
+ return false;
+
+ if (found->hasOwnProperty(name)) {
+ const QQmlJSMetaProperty prop = found->ownProperty(name);
+ if (!isRevisionAllowed(prop.revision(), scope))
+ return false;
- const QList<QQmlJSMetaPropertyBinding> defaultPropBindings =
- parent->propertyBindings(defaultPropertyName);
- for (const QQmlJSMetaPropertyBinding &binding : defaultPropBindings) {
- if (binding.bindingType() == QQmlJSMetaPropertyBinding::Object
- && equals(binding.objectType(), child)) {
+ result = resolveParentProperty(name, base, prop.type());
return true;
}
+
+ if (found->hasOwnMethod(name)) {
+ const auto methods = found->ownMethods(name);
+ for (const auto &method : methods) {
+ if (isRevisionAllowed(method.revision(), scope)) {
+ result = jsValueType();
+ return true;
+ }
+ }
+ }
+
+ return false;
+ })) {
+ return result;
}
- return false;
- };
+ }
+
+ if (QQmlJSScope::ConstPtr result = containedTypeForName(name))
+ return result;
+
+ if (m_jsGlobalObject->hasProperty(name))
+ return m_jsGlobalObject->property(name).type();
- if (QQmlJSScope::ConstPtr identified = scopeForId(name, scope)) {
- return QQmlJSRegisterContent::create(storedType(identified), identified,
- QQmlJSRegisterContent::ObjectById, scope);
+ if (m_jsGlobalObject->hasMethod(name))
+ return jsValueType();
+
+ return {};
+}
+
+/*!
+ * \internal
+ *
+ * Same as the other scopedType method, but accepts a QQmlJSRegisterContent and
+ * also returns one. This way you not only get the type, but also the content
+ * variant and various meta info.
+ */
+QQmlJSRegisterContent QQmlJSTypeResolver::scopedType(const QQmlJSRegisterContent &scope,
+ const QString &name, int lookupIndex,
+ QQmlJSScopesByIdOptions options) const
+{
+ const QQmlJSScope::ConstPtr contained = scope.containedType();
+ if (QQmlJSScope::ConstPtr identified = m_objectsById.scope(name, contained, options)) {
+ return QQmlJSRegisterContent::create(
+ identified, lookupIndex, QQmlJSRegisterContent::ObjectById, contained);
}
- if (QQmlJSScope::ConstPtr base = QQmlJSScope::findCurrentQMLScope(scope)) {
+ if (QQmlJSScope::ConstPtr base = QQmlJSScope::findCurrentQMLScope(contained)) {
QQmlJSRegisterContent result;
if (QQmlJSUtils::searchBaseAndExtensionTypes(
base, [&](const QQmlJSScope::ConstPtr &found, QQmlJSScope::ExtensionKind mode) {
- if (mode == QQmlJSScope::ExtensionNamespace) // no use for it here
- return false;
- if (found->hasOwnProperty(name)) {
- QQmlJSMetaProperty prop = found->ownProperty(name);
- if (!isRevisionAllowed(prop.revision(), scope))
- return false;
- if (m_parentMode == UseDocumentParent
- && name == base->parentPropertyName()) {
- QQmlJSScope::ConstPtr baseParent = base->parentScope();
- if (baseParent && baseParent->inherits(prop.type())
- && isAssignedToDefaultProperty(baseParent, base)) {
- prop.setType(baseParent);
- }
- }
- result = QQmlJSRegisterContent::create(
- storedType(prop.type()),
- prop, scopeContentVariant(mode, false), scope);
- return true;
- }
-
- if (found->hasOwnMethod(name)) {
- auto methods = found->ownMethods(name);
- for (auto it = methods.begin(); it != methods.end();) {
- if (!isRevisionAllowed(it->revision(), scope))
- it = methods.erase(it);
- else
- ++it;
- }
- if (methods.isEmpty())
- return false;
- result = QQmlJSRegisterContent::create(
- jsValueType(), methods, scopeContentVariant(mode, true), scope);
- return true;
- }
-
- // Unqualified enums are not allowed
-
- return false;
- })) {
+ if (mode == QQmlJSScope::ExtensionNamespace) // no use for it here
+ return false;
+
+ if (found->hasOwnProperty(name)) {
+ QQmlJSMetaProperty prop = found->ownProperty(name);
+ if (!isRevisionAllowed(prop.revision(), contained))
+ return false;
+
+ prop.setType(resolveParentProperty(name, base, prop.type()));
+ result = QQmlJSRegisterContent::create(
+ prop, QQmlJSRegisterContent::InvalidLookupIndex, lookupIndex,
+ scopeContentVariant(mode, false), contained);
+ return true;
+ }
+
+ if (found->hasOwnMethod(name)) {
+ auto methods = found->ownMethods(name);
+ for (auto it = methods.begin(); it != methods.end();) {
+ if (!isRevisionAllowed(it->revision(), contained))
+ it = methods.erase(it);
+ else
+ ++it;
+ }
+ if (methods.isEmpty())
+ return false;
+ result = QQmlJSRegisterContent::create(
+ methods, jsValueType(), scopeContentVariant(mode, true), contained);
+ return true;
+ }
+
+ // Unqualified enums are not allowed
+ return false;
+ })) {
return result;
}
}
- QQmlJSRegisterContent result = referenceTypeForName(name);
+ QQmlJSRegisterContent result = registerContentForName(name);
+
if (result.isValid())
return result;
if (m_jsGlobalObject->hasProperty(name)) {
- return QQmlJSRegisterContent::create(jsValueType(), m_jsGlobalObject->property(name),
- QQmlJSRegisterContent::JavaScriptGlobal,
- m_jsGlobalObject);
+ return QQmlJSRegisterContent::create(
+ m_jsGlobalObject->property(name), QQmlJSRegisterContent::InvalidLookupIndex,
+ lookupIndex, QQmlJSRegisterContent::JavaScriptGlobal, m_jsGlobalObject);
} else if (m_jsGlobalObject->hasMethod(name)) {
- return QQmlJSRegisterContent::create(jsValueType(), m_jsGlobalObject->methods(name),
- QQmlJSRegisterContent::JavaScriptGlobal,
- m_jsGlobalObject);
+ return QQmlJSRegisterContent::create(
+ m_jsGlobalObject->methods(name), jsValueType(),
+ QQmlJSRegisterContent::JavaScriptGlobal, m_jsGlobalObject);
}
return {};
}
+/*!
+ * \fn QQmlJSScope::ConstPtr typeForId(const QQmlJSScope::ConstPtr &scope, const QString &name, QQmlJSScopesByIdOptions options) const
+ *
+ * \internal
+ *
+ * Same as scopedType(), but assumes that the \a name is an ID and only searches
+ * the context.
+ *
+ * TODO: This is just as wrong as scopedType() in that it disregards both scope
+ * properties overriding context properties and run time context
+ * properties.
+ */
+
bool QQmlJSTypeResolver::checkEnums(const QQmlJSScope::ConstPtr &scope, const QString &name,
QQmlJSRegisterContent *result,
QQmlJSScope::ExtensionKind mode) const
@@ -876,23 +1210,22 @@ bool QQmlJSTypeResolver::checkEnums(const QQmlJSScope::ConstPtr &scope, const QS
if (name.isEmpty() || !name.at(0).isUpper())
return false;
- const bool inExtension =
- (mode == QQmlJSScope::ExtensionType) || (mode == QQmlJSScope::ExtensionNamespace);
+ const bool inExtension = (mode != QQmlJSScope::NotExtension);
const auto enums = scope->ownEnumerations();
for (const auto &enumeration : enums) {
- if (enumeration.name() == name) {
+ if ((enumeration.isScoped() || enumeration.isQml()) && enumeration.name() == name) {
*result = QQmlJSRegisterContent::create(
- storedType(intType()), enumeration, QString(),
+ enumeration, QString(),
inExtension ? QQmlJSRegisterContent::ExtensionObjectEnum
: QQmlJSRegisterContent::ObjectEnum,
scope);
return true;
}
- if (enumeration.hasKey(name)) {
+ if (!enumeration.isScoped() && enumeration.hasKey(name)) {
*result = QQmlJSRegisterContent::create(
- storedType(intType()), enumeration, name,
+ enumeration, name,
inExtension ? QQmlJSRegisterContent::ExtensionObjectEnum
: QQmlJSRegisterContent::ObjectEnum,
scope);
@@ -903,6 +1236,143 @@ bool QQmlJSTypeResolver::checkEnums(const QQmlJSScope::ConstPtr &scope, const QS
return false;
}
+bool QQmlJSTypeResolver::canPopulate(
+ const QQmlJSScope::ConstPtr &type, const QQmlJSScope::ConstPtr &passedArgumentType,
+ bool *isExtension) const
+{
+ // TODO: We could allow QVariantMap and QVariantHash to be populated, but that needs extra
+ // code in the code generator.
+
+ if (type.isNull()
+ || canHold(passedArgumentType, type)
+ || isPrimitive(passedArgumentType)
+ || type->accessSemantics() != QQmlJSScope::AccessSemantics::Value
+ || !type->isStructured()) {
+ return false;
+ }
+
+ if (isExtension)
+ *isExtension = !type->extensionType().scope.isNull();
+
+ return true;
+}
+
+QQmlJSMetaMethod QQmlJSTypeResolver::selectConstructor(
+ const QQmlJSScope::ConstPtr &type, const QQmlJSScope::ConstPtr &passedArgumentType,
+ bool *isExtension) const
+{
+ // If the "from" type can hold the target type, we should not try to coerce
+ // it to any constructor argument.
+ if (type.isNull()
+ || canHold(passedArgumentType, type)
+ || type->accessSemantics() != QQmlJSScope::AccessSemantics::Value
+ || !type->isCreatable()) {
+ return QQmlJSMetaMethod();
+ }
+
+ auto doSelectConstructor = [&](const QQmlJSScope::ConstPtr &type) {
+ QQmlJSMetaMethod candidate;
+
+ const auto ownMethods = type->ownMethods();
+ for (const QQmlJSMetaMethod &method : ownMethods) {
+ if (!method.isConstructor())
+ continue;
+
+ const auto index = method.constructorIndex();
+ Q_ASSERT(index != QQmlJSMetaMethod::RelativeFunctionIndex::Invalid);
+
+ const auto methodArguments = method.parameters();
+ if (methodArguments.size() != 1)
+ continue;
+
+ const QQmlJSScope::ConstPtr methodArgumentType = methodArguments[0].type();
+
+ if (equals(passedArgumentType, methodArgumentType))
+ return method;
+
+ // Do not select further ctors here. We don't want to do multi-step construction as that
+ // is confusing and easily leads to infinite recursion.
+ if (!candidate.isValid()
+ && canPrimitivelyConvertFromTo(passedArgumentType, methodArgumentType)) {
+ candidate = method;
+ }
+ }
+
+ return candidate;
+ };
+
+ if (QQmlJSScope::ConstPtr extension = type->extensionType().scope) {
+ const QQmlJSMetaMethod ctor = doSelectConstructor(extension);
+ if (ctor.isValid()) {
+ if (isExtension)
+ *isExtension = true;
+ return ctor;
+ }
+ }
+
+ if (isExtension)
+ *isExtension = false;
+
+ return doSelectConstructor(type);
+}
+
+bool QQmlJSTypeResolver::areEquivalentLists(
+ const QQmlJSScope::ConstPtr &a, const QQmlJSScope::ConstPtr &b) const
+{
+ const QQmlJSScope::ConstPtr equivalentLists[2][2] = {
+ { m_stringListType, m_stringType->listType() },
+ { m_variantListType, m_varType->listType() }
+ };
+
+ for (const auto eq : equivalentLists) {
+ if ((equals(a, eq[0]) && equals(b, eq[1])) || (equals(a, eq[1]) && equals(b, eq[0])))
+ return true;
+ }
+
+ return false;
+}
+
+bool QQmlJSTypeResolver::isTriviallyCopyable(const QQmlJSScope::ConstPtr &type) const
+{
+ // pointers are trivially copyable
+ if (type->isReferenceType())
+ return true;
+
+ // Enum values are trivially copyable
+ if (type->scopeType() == QQmlSA::ScopeType::EnumScope)
+ return true;
+
+ for (const QQmlJSScope::ConstPtr &trivial : {
+ m_nullType, m_voidType,
+ m_boolType, m_metaObjectType,
+ m_realType, m_floatType,
+ m_int8Type, m_uint8Type,
+ m_int16Type, m_uint16Type,
+ m_int32Type, m_uint32Type,
+ m_int64Type, m_uint64Type }) {
+ if (equals(type, trivial))
+ return true;
+ }
+
+ return false;
+}
+
+bool QQmlJSTypeResolver::inherits(const QQmlJSScope::ConstPtr &derived, const QQmlJSScope::ConstPtr &base) const
+{
+ const bool matchByName = !base->isComposite();
+ for (QQmlJSScope::ConstPtr derivedBase = derived; derivedBase;
+ derivedBase = derivedBase->baseType()) {
+ if (equals(derivedBase, base))
+ return true;
+ if (matchByName
+ && !derivedBase->isComposite()
+ && derivedBase->internalName() == base->internalName()) {
+ return true;
+ }
+ }
+ return false;
+}
+
bool QQmlJSTypeResolver::canPrimitivelyConvertFromTo(
const QQmlJSScope::ConstPtr &from, const QQmlJSScope::ConstPtr &to) const
{
@@ -912,12 +1382,14 @@ bool QQmlJSTypeResolver::canPrimitivelyConvertFromTo(
return true;
if (equals(from, m_jsValueType) || equals(to, m_jsValueType))
return true;
+ if (equals(to, m_qQmlScriptStringType))
+ return true;
if (isNumeric(from) && isNumeric(to))
return true;
if (isNumeric(from) && equals(to, m_boolType))
return true;
if (from->accessSemantics() == QQmlJSScope::AccessSemantics::Reference
- && equals(to, m_boolType)) {
+ && (equals(to, m_boolType) || equals(to, m_stringType))) {
return true;
}
@@ -925,6 +1397,10 @@ bool QQmlJSTypeResolver::canPrimitivelyConvertFromTo(
if (isNumeric(from) && equals(to, m_stringType))
return true;
+ // We can convert strings to numbers, but not to enums
+ if (equals(from, m_stringType) && isNumeric(to))
+ return to->scopeType() != QQmlJSScope::ScopeType::EnumScope;
+
// We can always convert between strings and urls.
if ((equals(from, m_stringType) && equals(to, m_urlType))
|| (equals(from, m_urlType) && equals(to, m_stringType))) {
@@ -937,25 +1413,26 @@ bool QQmlJSTypeResolver::canPrimitivelyConvertFromTo(
return true;
}
- if (equals(from, m_voidType) || equals(to, m_voidType))
+ if (equals(to, m_voidType))
return true;
if (to.isNull())
- return false;
+ return equals(from, m_voidType);
- if (equals(from, m_stringType) && equals(to, m_dateTimeType))
- return true;
-
- for (const auto &originType : {m_dateTimeType, m_dateType, m_timeType}) {
+ const auto types = { m_dateTimeType, m_dateType, m_timeType, m_stringType };
+ for (const auto &originType : types) {
if (!equals(from, originType))
continue;
- for (const auto &targetType : {m_dateTimeType, m_dateType, m_timeType, m_stringType}) {
+ for (const auto &targetType : types) {
if (equals(to, targetType))
return true;
}
- break;;
+ if (equals(to, m_realType))
+ return true;
+
+ break;
}
if (equals(from, m_nullType)
@@ -971,7 +1448,7 @@ bool QQmlJSTypeResolver::canPrimitivelyConvertFromTo(
if (equals(to, m_jsPrimitiveType))
return isPrimitive(from);
- if (equals(from, m_emptyListType) || equals(from, m_variantListType))
+ if (equals(from, m_variantListType))
return to->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence;
const bool matchByName = !to->isComposite();
@@ -987,6 +1464,24 @@ bool QQmlJSTypeResolver::canPrimitivelyConvertFromTo(
if (canConvertFromTo(from, m_jsPrimitiveType) && canConvertFromTo(m_jsPrimitiveType, to))
return true;
+ // We can convert everything to bool.
+ if (equals(to, m_boolType))
+ return true;
+
+ if (areEquivalentLists(from, to))
+ return true;
+
+ if (from->isListProperty()
+ && to->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence
+ && canConvertFromTo(from->valueType(), to->valueType())) {
+ return true;
+ }
+
+ if (equals(to, m_stringType)
+ && from->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence) {
+ return canConvertFromTo(from->valueType(), m_stringType);
+ }
+
return false;
}
@@ -995,14 +1490,17 @@ QQmlJSRegisterContent QQmlJSTypeResolver::lengthProperty(
{
QQmlJSMetaProperty prop;
prop.setPropertyName(u"length"_s);
- prop.setTypeName(u"int"_s);
- prop.setType(intType());
+ prop.setTypeName(u"qsizetype"_s);
+ prop.setType(sizeType());
prop.setIsWritable(isWritable);
- return QQmlJSRegisterContent::create(intType(), prop, QQmlJSRegisterContent::Builtin, scope);
+ return QQmlJSRegisterContent::create(
+ prop, QQmlJSRegisterContent::InvalidLookupIndex,
+ QQmlJSRegisterContent::InvalidLookupIndex, QQmlJSRegisterContent::Builtin, scope);
}
-QQmlJSRegisterContent QQmlJSTypeResolver::memberType(const QQmlJSScope::ConstPtr &type,
- const QString &name) const
+QQmlJSRegisterContent QQmlJSTypeResolver::memberType(
+ const QQmlJSScope::ConstPtr &type, const QString &name, int baseLookupIndex,
+ int resultLookupIndex) const
{
QQmlJSRegisterContent result;
@@ -1010,14 +1508,26 @@ QQmlJSRegisterContent QQmlJSTypeResolver::memberType(const QQmlJSScope::ConstPtr
if (equals(type, metaObjectType()))
return {};
+ if (equals(type, variantMapType())) {
+ QQmlJSMetaProperty prop;
+ prop.setPropertyName(name);
+ prop.setTypeName(u"QVariant"_s);
+ prop.setType(varType());
+ prop.setIsWritable(true);
+ return QQmlJSRegisterContent::create(
+ prop, baseLookupIndex, resultLookupIndex,
+ QQmlJSRegisterContent::GenericObjectProperty, type);
+ }
+
if (equals(type, jsValueType())) {
QQmlJSMetaProperty prop;
prop.setPropertyName(name);
prop.setTypeName(u"QJSValue"_s);
prop.setType(jsValueType());
prop.setIsWritable(true);
- return QQmlJSRegisterContent::create(jsValueType(), prop,
- QQmlJSRegisterContent::JavaScriptObjectProperty, type);
+ return QQmlJSRegisterContent::create(
+ prop, baseLookupIndex, resultLookupIndex,
+ QQmlJSRegisterContent::GenericObjectProperty, type);
}
if ((equals(type, stringType())
@@ -1031,8 +1541,7 @@ QQmlJSRegisterContent QQmlJSTypeResolver::memberType(const QQmlJSScope::ConstPtr
if (scope->hasOwnProperty(name)) {
const auto prop = scope->ownProperty(name);
result = QQmlJSRegisterContent::create(
- storedType(prop.type()),
- prop,
+ prop, baseLookupIndex, resultLookupIndex,
mode == QQmlJSScope::NotExtension
? QQmlJSRegisterContent::ObjectProperty
: QQmlJSRegisterContent::ExtensionObjectProperty,
@@ -1043,27 +1552,13 @@ QQmlJSRegisterContent QQmlJSTypeResolver::memberType(const QQmlJSScope::ConstPtr
if (scope->hasOwnMethod(name)) {
const auto methods = scope->ownMethods(name);
result = QQmlJSRegisterContent::create(
- jsValueType(), methods,
+ methods, jsValueType(),
mode == QQmlJSScope::NotExtension
? QQmlJSRegisterContent::ObjectMethod
: QQmlJSRegisterContent::ExtensionObjectMethod,
scope);
return true;
}
-
- if (std::optional<QQmlJSScope::JavaScriptIdentifier> identifier =
- scope->findJSIdentifier(name);
- identifier.has_value()) {
- QQmlJSMetaProperty prop;
- prop.setPropertyName(name);
- prop.setTypeName(u"QJSValue"_s);
- prop.setType(jsValueType());
- prop.setIsWritable(!identifier->isConst);
-
- result = QQmlJSRegisterContent::create(
- jsValueType(), prop, QQmlJSRegisterContent::JavaScriptObject, type);
- return true;
- }
}
return checkEnums(scope, name, &result, mode);
@@ -1072,6 +1567,23 @@ QQmlJSRegisterContent QQmlJSTypeResolver::memberType(const QQmlJSScope::ConstPtr
if (QQmlJSUtils::searchBaseAndExtensionTypes(type, check))
return result;
+ for (auto scope = type;
+ scope && (scope->scopeType() == QQmlSA::ScopeType::JSFunctionScope
+ || scope->scopeType() == QQmlSA::ScopeType::JSLexicalScope);
+ scope = scope->parentScope()) {
+ if (auto ownIdentifier = scope->ownJSIdentifier(name)) {
+ QQmlJSMetaProperty prop;
+ prop.setPropertyName(name);
+ prop.setTypeName(u"QJSValue"_s);
+ prop.setType(jsValueType());
+ prop.setIsWritable(!(ownIdentifier.value().isConst));
+
+ return QQmlJSRegisterContent::create(
+ prop, baseLookupIndex, resultLookupIndex,
+ QQmlJSRegisterContent::JavaScriptObject, scope);
+ }
+ }
+
if (QQmlJSScope::ConstPtr attachedBase = typeForName(name)) {
if (QQmlJSScope::ConstPtr attached = attachedBase->attachedType()) {
if (!genericType(attached)) {
@@ -1085,9 +1597,9 @@ QQmlJSRegisterContent QQmlJSTypeResolver::memberType(const QQmlJSScope::ConstPtr
qmlCompiler, type->sourceLocation());
return {};
} else {
- return QQmlJSRegisterContent::create(storedType(attached), attached,
- QQmlJSRegisterContent::ObjectAttached,
- attachedBase);
+ return QQmlJSRegisterContent::create(
+ attached, resultLookupIndex, QQmlJSRegisterContent::ObjectAttached,
+ attachedBase);
}
}
}
@@ -1110,12 +1622,12 @@ QQmlJSRegisterContent QQmlJSTypeResolver::memberEnumType(const QQmlJSScope::Cons
return {};
}
-QQmlJSRegisterContent QQmlJSTypeResolver::memberType(const QQmlJSRegisterContent &type,
- const QString &name) const
+QQmlJSRegisterContent QQmlJSTypeResolver::memberType(
+ const QQmlJSRegisterContent &type, const QString &name, int lookupIndex) const
{
if (type.isType()) {
const auto content = type.type();
- const auto result = memberType(content, name);
+ const auto result = memberType(content, name, type.resultLookupIndex(), lookupIndex);
if (result.isValid())
return result;
@@ -1124,13 +1636,13 @@ QQmlJSRegisterContent QQmlJSTypeResolver::memberType(const QQmlJSRegisterContent
return memberEnumType(type.scopeType(), name);
}
if (type.isProperty())
- return memberType(type.property().type(), name);
+ return memberType(type.property().type(), name, type.resultLookupIndex(), lookupIndex);
if (type.isEnumeration()) {
const auto enumeration = type.enumeration();
if (!type.enumMember().isEmpty() || !enumeration.hasKey(name))
return {};
- return QQmlJSRegisterContent::create(storedType(intType()), enumeration, name,
- QQmlJSRegisterContent::ObjectEnum, type.scopeType());
+ return QQmlJSRegisterContent::create(
+ enumeration, name, QQmlJSRegisterContent::ObjectEnum, type.scopeType());
}
if (type.isMethod()) {
QQmlJSMetaProperty prop;
@@ -1138,9 +1650,9 @@ QQmlJSRegisterContent QQmlJSTypeResolver::memberType(const QQmlJSRegisterContent
prop.setPropertyName(name);
prop.setType(jsValueType());
prop.setIsWritable(true);
- return QQmlJSRegisterContent::create(jsValueType(), prop,
- QQmlJSRegisterContent::JavaScriptObjectProperty,
- jsValueType());
+ return QQmlJSRegisterContent::create(
+ prop, QQmlJSRegisterContent::InvalidLookupIndex, lookupIndex,
+ QQmlJSRegisterContent::GenericObjectProperty, jsValueType());
}
if (type.isImportNamespace()) {
if (type.scopeType()->accessSemantics() != QQmlJSScope::AccessSemantics::Reference) {
@@ -1150,13 +1662,35 @@ QQmlJSRegisterContent QQmlJSTypeResolver::memberType(const QQmlJSRegisterContent
return {};
}
- return referenceTypeForName(
+ return registerContentForName(
name, type.scopeType(),
type.variant() == QQmlJSRegisterContent::ObjectModulePrefix);
}
if (type.isConversion()) {
- const auto result = memberType(type.conversionResult(), name);
- return result.isValid() ? result : memberEnumType(type.scopeType(), name);
+ if (const auto result = memberType(
+ type.conversionResult(), name, type.resultLookupIndex(), lookupIndex);
+ result.isValid()) {
+ return result;
+ }
+
+ if (const auto result = memberEnumType(type.scopeType(), name); result.isValid())
+ return result;
+
+ // If the conversion consists of only undefined and one actual type,
+ // we can produce the members of that one type.
+ // If the value is then actually undefined, the result is an exception.
+
+ auto origins = type.conversionOrigins();
+ const auto begin = origins.begin();
+ const auto end = std::remove_if(begin, origins.end(),
+ [this](const QQmlJSScope::ConstPtr &origin) {
+ return equals(origin, m_voidType);
+ });
+
+ // If the conversion cannot hold the original type, it loses information.
+ return (end - begin == 1 && canHold(type.conversionResult(), *begin))
+ ? memberType(*begin, name, type.resultLookupIndex(), lookupIndex)
+ : QQmlJSRegisterContent();
}
Q_UNREACHABLE_RETURN({});
@@ -1167,13 +1701,22 @@ QQmlJSRegisterContent QQmlJSTypeResolver::valueType(const QQmlJSRegisterContent
QQmlJSScope::ConstPtr scope;
QQmlJSScope::ConstPtr value;
- auto valueType = [this](const QQmlJSScope::ConstPtr &scope) {
+ auto valueType = [&](const QQmlJSScope::ConstPtr &scope) {
if (scope->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence)
return scope->valueType();
- else if (equals(scope, m_jsValueType) || equals(scope, m_varType))
+
+ if (equals(scope, m_forInIteratorPtr))
+ return m_sizeType;
+
+ if (equals(scope, m_forOfIteratorPtr))
+ return list.scopeType()->valueType();
+
+ if (equals(scope, m_jsValueType) || equals(scope, m_varType))
return m_jsValueType;
- else if (equals(scope, m_stringType))
+
+ if (equals(scope, m_stringType))
return m_stringType;
+
return QQmlJSScope::ConstPtr();
};
@@ -1197,7 +1740,8 @@ QQmlJSRegisterContent QQmlJSTypeResolver::valueType(const QQmlJSRegisterContent
property.setType(value);
return QQmlJSRegisterContent::create(
- storedType(value), property, QQmlJSRegisterContent::ListValue, scope);
+ property, QQmlJSRegisterContent::InvalidLookupIndex,
+ QQmlJSRegisterContent::InvalidLookupIndex, QQmlJSRegisterContent::ListValue, scope);
}
QQmlJSRegisterContent QQmlJSTypeResolver::returnType(
@@ -1206,13 +1750,32 @@ QQmlJSRegisterContent QQmlJSTypeResolver::returnType(
{
Q_ASSERT(variant == QQmlJSRegisterContent::MethodReturnValue
|| variant == QQmlJSRegisterContent::JavaScriptReturnValue);
- return QQmlJSRegisterContent::create(storedType(type), type, variant, scope);
+ return QQmlJSRegisterContent::create(
+ type, QQmlJSRegisterContent::InvalidLookupIndex, variant, scope);
}
-bool QQmlJSTypeResolver::registerIsStoredIn(
- const QQmlJSRegisterContent &reg, const QQmlJSScope::ConstPtr &type) const
+QQmlJSRegisterContent QQmlJSTypeResolver::iteratorPointer(
+ const QQmlJSRegisterContent &listType, QQmlJS::AST::ForEachType type,
+ int lookupIndex) const
{
- return equals(reg.storedType(), type);
+ const QQmlJSScope::ConstPtr value = (type == QQmlJS::AST::ForEachType::In)
+ ? m_int32Type
+ : valueType(listType).containedType();
+
+ QQmlJSScope::ConstPtr iteratorPointer = type == QQmlJS::AST::ForEachType::In
+ ? m_forInIteratorPtr
+ : m_forOfIteratorPtr;
+
+ const QQmlJSScope::ConstPtr listContained = listType.containedType();
+
+ QQmlJSMetaProperty prop;
+ prop.setPropertyName(u"<>"_s);
+ prop.setTypeName(iteratorPointer->internalName());
+ prop.setType(iteratorPointer);
+ return QQmlJSRegisterContent::create(
+ prop, lookupIndex,
+ QQmlJSRegisterContent::InvalidLookupIndex, QQmlJSRegisterContent::ListIterator,
+ listContained);
}
bool QQmlJSTypeResolver::registerContains(const QQmlJSRegisterContent &reg,
@@ -1275,13 +1838,20 @@ QQmlJSRegisterContent QQmlJSTypeResolver::convert(
{
if (from.isConversion()) {
return QQmlJSRegisterContent::create(
- to.storedType(), from.conversionOrigins(), containedType(to), from.variant(),
- from.scopeType());
+ from.conversionOrigins(), to.containedType(),
+ to.scopeType() ? to.scopeType() : from.conversionResultScope(),
+ from.variant(), from.scopeType());
}
return QQmlJSRegisterContent::create(
- to.storedType(), QList<QQmlJSScope::ConstPtr>{containedType(from)},
- containedType(to), from.variant(), from.scopeType());
+ QList<QQmlJSScope::ConstPtr>{from.containedType()},
+ to.containedType(), to.scopeType(), from.variant(), from.scopeType());
+}
+
+QQmlJSRegisterContent QQmlJSTypeResolver::cast(
+ const QQmlJSRegisterContent &from, const QQmlJSScope::ConstPtr &to) const
+{
+ return from.castTo(to);
}
QQmlJSScope::ConstPtr QQmlJSTypeResolver::comparableType(const QQmlJSScope::ConstPtr &type) const
diff --git a/src/qmlcompiler/qqmljstyperesolver_p.h b/src/qmlcompiler/qqmljstyperesolver_p.h
index 6c3583ca73..49aecc716d 100644
--- a/src/qmlcompiler/qqmljstyperesolver_p.h
+++ b/src/qmlcompiler/qqmljstyperesolver_p.h
@@ -14,20 +14,22 @@
//
// We mean it.
-#include <private/qtqmlcompilerexports_p.h>
+#include <memory>
+#include <qtqmlcompilerexports.h>
#include <private/qqmlirbuilder_p.h>
#include <private/qqmljsast_p.h>
#include "qqmljsimporter_p.h"
#include "qqmljslogger_p.h"
#include "qqmljsregistercontent_p.h"
+#include "qqmljsresourcefilemapper_p.h"
#include "qqmljsscope_p.h"
#include "qqmljsscopesbyid_p.h"
QT_BEGIN_NAMESPACE
class QQmlJSImportVisitor;
-class Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSTypeResolver
+class Q_QMLCOMPILER_EXPORT QQmlJSTypeResolver
{
public:
enum ParentMode { UseDocumentParent, UseParentProperty };
@@ -41,12 +43,18 @@ public:
QQmlJSScope::ConstPtr voidType() const { return m_voidType; }
QQmlJSScope::ConstPtr emptyType() const { return m_emptyType; }
- QQmlJSScope::ConstPtr emptyListType() const { return m_emptyListType; }
QQmlJSScope::ConstPtr nullType() const { return m_nullType; }
QQmlJSScope::ConstPtr realType() const { return m_realType; }
QQmlJSScope::ConstPtr floatType() const { return m_floatType; }
- QQmlJSScope::ConstPtr intType() const { return m_intType; }
- QQmlJSScope::ConstPtr uintType() const { return m_uintType; }
+ QQmlJSScope::ConstPtr int8Type() const { return m_int8Type; }
+ QQmlJSScope::ConstPtr uint8Type() const { return m_uint8Type; }
+ QQmlJSScope::ConstPtr int16Type() const { return m_int16Type; }
+ QQmlJSScope::ConstPtr uint16Type() const { return m_uint16Type; }
+ QQmlJSScope::ConstPtr int32Type() const { return m_int32Type; }
+ QQmlJSScope::ConstPtr uint32Type() const { return m_uint32Type; }
+ QQmlJSScope::ConstPtr int64Type() const { return m_int64Type; }
+ QQmlJSScope::ConstPtr uint64Type() const { return m_uint64Type; }
+ QQmlJSScope::ConstPtr sizeType() const { return m_sizeType; }
QQmlJSScope::ConstPtr boolType() const { return m_boolType; }
QQmlJSScope::ConstPtr stringType() const { return m_stringType; }
QQmlJSScope::ConstPtr stringListType() const { return m_stringListType; }
@@ -56,6 +64,7 @@ public:
QQmlJSScope::ConstPtr dateType() const { return m_dateType; }
QQmlJSScope::ConstPtr timeType() const { return m_timeType; }
QQmlJSScope::ConstPtr variantListType() const { return m_variantListType; }
+ QQmlJSScope::ConstPtr variantMapType() const { return m_variantMapType; }
QQmlJSScope::ConstPtr varType() const { return m_varType; }
QQmlJSScope::ConstPtr jsValueType() const { return m_jsValueType; }
QQmlJSScope::ConstPtr jsPrimitiveType() const { return m_jsPrimitiveType; }
@@ -63,32 +72,55 @@ public:
QQmlJSScope::ConstPtr metaObjectType() const { return m_metaObjectType; }
QQmlJSScope::ConstPtr functionType() const { return m_functionType; }
QQmlJSScope::ConstPtr jsGlobalObject() const { return m_jsGlobalObject; }
+ QQmlJSScope::ConstPtr qObjectType() const { return m_qObjectType; }
QQmlJSScope::ConstPtr qObjectListType() const { return m_qObjectListType; }
+ QQmlJSScope::ConstPtr arrayPrototype() const { return m_arrayPrototype; }
+ QQmlJSScope::ConstPtr forInIteratorPtr() const { return m_forInIteratorPtr; }
+ QQmlJSScope::ConstPtr forOfIteratorPtr() const { return m_forOfIteratorPtr; }
+
+ QQmlJSScope::ConstPtr mathObject() const;
+ QQmlJSScope::ConstPtr consoleObject() const;
QQmlJSScope::ConstPtr scopeForLocation(const QV4::CompiledData::Location &location) const;
- QQmlJSScope::ConstPtr scopeForId(
- const QString &id, const QQmlJSScope::ConstPtr &referrer) const;
bool isPrefix(const QString &name) const
{
return m_imports.hasType(name) && !m_imports.type(name).scope;
}
+ const QHash<QString, QQmlJS::ImportedScope<QQmlJSScope::ConstPtr>> &importedTypes() const
+ {
+ return m_imports.types();
+ }
QQmlJSScope::ConstPtr typeForName(const QString &name) const
{
return m_imports.type(name).scope;
}
+ QString nameForType(const QQmlJSScope::ConstPtr &type) const
+ {
+ return m_imports.name(originalType(type));
+ }
+
QQmlJSScope::ConstPtr typeFromAST(QQmlJS::AST::Type *type) const;
QQmlJSScope::ConstPtr typeForConst(QV4::ReturnedValue rv) const;
QQmlJSRegisterContent typeForBinaryOperation(QSOperator::Op oper,
const QQmlJSRegisterContent &left,
const QQmlJSRegisterContent &right) const;
+ QQmlJSScope::ConstPtr typeForId(
+ const QQmlJSScope::ConstPtr &scope, const QString &name,
+ QQmlJSScopesByIdOptions options = Default) const
+ {
+ return m_objectsById.scope(name, scope, options);
+ }
+
enum class UnaryOperator { Not, Plus, Minus, Increment, Decrement, Complement };
QQmlJSRegisterContent typeForArithmeticUnaryOperation(
UnaryOperator op, const QQmlJSRegisterContent &operand) const;
bool isPrimitive(const QQmlJSRegisterContent &type) const;
+ bool isPrimitive(const QQmlJSScope::ConstPtr &type) const;
+
bool isNumeric(const QQmlJSRegisterContent &type) const;
bool isIntegral(const QQmlJSRegisterContent &type) const;
@@ -104,20 +136,29 @@ public:
QQmlJSRegisterContent builtinType(const QQmlJSScope::ConstPtr &type) const;
QQmlJSRegisterContent globalType(const QQmlJSScope::ConstPtr &type) const;
- QQmlJSRegisterContent scopedType(const QQmlJSScope::ConstPtr &scope, const QString &name) const;
- QQmlJSRegisterContent memberType(const QQmlJSRegisterContent &type, const QString &name) const;
+ QQmlJSScope::ConstPtr scopedType(
+ const QQmlJSScope::ConstPtr &scope, const QString &name,
+ QQmlJSScopesByIdOptions options = Default) const;
+
+ QQmlJSRegisterContent scopedType(
+ const QQmlJSRegisterContent &scope, const QString &name,
+ int lookupIndex = QQmlJSRegisterContent::InvalidLookupIndex,
+ QQmlJSScopesByIdOptions options = Default) const;
+
+ QQmlJSRegisterContent memberType(
+ const QQmlJSRegisterContent &type, const QString &name,
+ int lookupIndex = QQmlJSRegisterContent::InvalidLookupIndex) const;
QQmlJSRegisterContent valueType(const QQmlJSRegisterContent &list) const;
QQmlJSRegisterContent returnType(
const QQmlJSScope::ConstPtr &type, QQmlJSRegisterContent::ContentVariant variant,
const QQmlJSScope::ConstPtr &scope) const;
- bool registerIsStoredIn(const QQmlJSRegisterContent &reg,
- const QQmlJSScope::ConstPtr &type) const;
+ QQmlJSRegisterContent iteratorPointer(
+ const QQmlJSRegisterContent &listType, QQmlJS::AST::ForEachType type,
+ int lookupIndex) const;
+
bool registerContains(const QQmlJSRegisterContent &reg,
const QQmlJSScope::ConstPtr &type) const;
- QQmlJSScope::ConstPtr containedType(const QQmlJSRegisterContent &container) const;
- QString containedTypeName(const QQmlJSRegisterContent &container,
- bool useFancyName = false) const;
QQmlJSRegisterContent tracked(const QQmlJSRegisterContent &type) const;
QQmlJSRegisterContent original(const QQmlJSRegisterContent &type) const;
@@ -125,10 +166,13 @@ public:
QQmlJSScope::ConstPtr trackedContainedType(const QQmlJSRegisterContent &container) const;
QQmlJSScope::ConstPtr originalContainedType(const QQmlJSRegisterContent &container) const;
- void adjustTrackedType(const QQmlJSScope::ConstPtr &tracked,
- const QQmlJSScope::ConstPtr &conversion) const;
- void adjustTrackedType(const QQmlJSScope::ConstPtr &tracked,
- const QList<QQmlJSScope::ConstPtr> &conversions) const;
+ [[nodiscard]] bool adjustTrackedType(
+ const QQmlJSScope::ConstPtr &tracked, const QQmlJSScope::ConstPtr &conversion) const;
+ [[nodiscard]] bool adjustTrackedType(
+ const QQmlJSScope::ConstPtr &tracked,
+ const QList<QQmlJSScope::ConstPtr> &conversions) const;
+ void adjustOriginalType(
+ const QQmlJSScope::ConstPtr &tracked, const QQmlJSScope::ConstPtr &conversion) const;
void generalizeType(const QQmlJSScope::ConstPtr &type) const;
void setParentMode(ParentMode mode) { m_parentMode = mode; }
@@ -144,7 +188,7 @@ public:
const QQmlJSScopesById &objectsById() const { return m_objectsById; }
bool canCallJSFunctions() const { return m_objectsById.signaturesAreEnforced(); }
- bool canUseValueTypes() const { return m_objectsById.valueTypesAreCopied(); }
+ bool canAddressValueTypes() const { return m_objectsById.valueTypesAreAddressable(); }
const QHash<QQmlJS::SourceLocation, QQmlJSMetaSignalHandler> &signalHandlers() const
{
@@ -155,19 +199,47 @@ public:
QQmlJSRegisterContent convert(
const QQmlJSRegisterContent &from, const QQmlJSRegisterContent &to) const;
+ QQmlJSRegisterContent cast(
+ const QQmlJSRegisterContent &from, const QQmlJSScope::ConstPtr &to) const;
QQmlJSScope::ConstPtr merge(const QQmlJSScope::ConstPtr &a,
const QQmlJSScope::ConstPtr &b) const;
bool canHoldUndefined(const QQmlJSRegisterContent &content) const;
+ bool isOptionalType(const QQmlJSRegisterContent &content) const;
+ QQmlJSScope::ConstPtr extractNonVoidFromOptionalType(
+ const QQmlJSRegisterContent &content) const;
+
bool isNumeric(const QQmlJSScope::ConstPtr &type) const;
+ bool isIntegral(const QQmlJSScope::ConstPtr &type) const;
+ bool isSignedInteger(const QQmlJSScope::ConstPtr &type) const;
+ bool isUnsignedInteger(const QQmlJSScope::ConstPtr &type) const;
+ bool isNativeArrayIndex(const QQmlJSScope::ConstPtr &type) const;
+
+ bool canHold(const QQmlJSScope::ConstPtr &container,
+ const QQmlJSScope::ConstPtr &contained) const;
+
+ bool canPopulate(
+ const QQmlJSScope::ConstPtr &type, const QQmlJSScope::ConstPtr &argument,
+ bool *isExtension) const;
+
+ QQmlJSMetaMethod selectConstructor(
+ const QQmlJSScope::ConstPtr &type, const QQmlJSScope::ConstPtr &argument,
+ bool *isExtension) const;
+
+ bool areEquivalentLists(const QQmlJSScope::ConstPtr &a, const QQmlJSScope::ConstPtr &b) const;
+
+ bool isTriviallyCopyable(const QQmlJSScope::ConstPtr &type) const;
+
+ bool inherits(const QQmlJSScope::ConstPtr &derived, const QQmlJSScope::ConstPtr &base) const;
protected:
- QQmlJSRegisterContent memberType(const QQmlJSScope::ConstPtr &type, const QString &name) const;
+ QQmlJSRegisterContent memberType(
+ const QQmlJSScope::ConstPtr &type, const QString &name,
+ int baseLookupIndex, int resultLookupIndex) const;
QQmlJSRegisterContent memberEnumType(const QQmlJSScope::ConstPtr &type,
const QString &name) const;
- bool isPrimitive(const QQmlJSScope::ConstPtr &type) const;
bool checkEnums(const QQmlJSScope::ConstPtr &scope, const QString &name,
QQmlJSRegisterContent *result, QQmlJSScope::ExtensionKind mode) const;
bool canPrimitivelyConvertFromTo(
@@ -177,21 +249,32 @@ protected:
const QQmlJSRegisterContent &origin,
QQmlJSScope::ConstPtr (QQmlJSTypeResolver::*op)(const QQmlJSScope::ConstPtr &) const) const;
- QQmlJSRegisterContent referenceTypeForName(
+ QQmlJSScope::ConstPtr containedTypeForName(const QString &name) const;
+ QQmlJSRegisterContent registerContentForName(
const QString &name,
const QQmlJSScope::ConstPtr &scopeType = QQmlJSScope::ConstPtr(),
bool hasObjectModuelPrefix = false) const;
+ QQmlJSScope::ConstPtr resolveParentProperty(
+ const QString &name, const QQmlJSScope::ConstPtr &base,
+ const QQmlJSScope::ConstPtr &propType) const;
+
QQmlJSScope::ConstPtr m_voidType;
- QQmlJSScope::ConstPtr m_emptyListType;
QQmlJSScope::ConstPtr m_emptyType;
QQmlJSScope::ConstPtr m_nullType;
QQmlJSScope::ConstPtr m_numberPrototype;
- QQmlJSScope::ConstPtr m_arrayType;
+ QQmlJSScope::ConstPtr m_arrayPrototype;
QQmlJSScope::ConstPtr m_realType;
QQmlJSScope::ConstPtr m_floatType;
- QQmlJSScope::ConstPtr m_intType;
- QQmlJSScope::ConstPtr m_uintType;
+ QQmlJSScope::ConstPtr m_int8Type;
+ QQmlJSScope::ConstPtr m_uint8Type;
+ QQmlJSScope::ConstPtr m_int16Type;
+ QQmlJSScope::ConstPtr m_uint16Type;
+ QQmlJSScope::ConstPtr m_int32Type;
+ QQmlJSScope::ConstPtr m_uint32Type;
+ QQmlJSScope::ConstPtr m_int64Type;
+ QQmlJSScope::ConstPtr m_uint64Type;
+ QQmlJSScope::ConstPtr m_sizeType;
QQmlJSScope::ConstPtr m_boolType;
QQmlJSScope::ConstPtr m_stringType;
QQmlJSScope::ConstPtr m_stringListType;
@@ -201,14 +284,19 @@ protected:
QQmlJSScope::ConstPtr m_dateType;
QQmlJSScope::ConstPtr m_timeType;
QQmlJSScope::ConstPtr m_variantListType;
+ QQmlJSScope::ConstPtr m_variantMapType;
QQmlJSScope::ConstPtr m_varType;
QQmlJSScope::ConstPtr m_jsValueType;
QQmlJSScope::ConstPtr m_jsPrimitiveType;
QQmlJSScope::ConstPtr m_listPropertyType;
+ QQmlJSScope::ConstPtr m_qObjectType;
QQmlJSScope::ConstPtr m_qObjectListType;
+ QQmlJSScope::ConstPtr m_qQmlScriptStringType;
QQmlJSScope::ConstPtr m_metaObjectType;
QQmlJSScope::ConstPtr m_functionType;
QQmlJSScope::ConstPtr m_jsGlobalObject;
+ QQmlJSScope::ConstPtr m_forInIteratorPtr;
+ QQmlJSScope::ConstPtr m_forOfIteratorPtr;
QQmlJSScopesById m_objectsById;
QHash<QV4::CompiledData::Location, QQmlJSScope::ConstPtr> m_objectsByLocation;
@@ -235,6 +323,19 @@ protected:
std::unique_ptr<QHash<QQmlJSScope::ConstPtr, TrackedType>> m_trackedTypes;
};
+/*!
+\internal
+
+QQmlJSTypeResolver expects to be outlived by its importer and mapper. It crashes when its importer
+or mapper gets destructed. Therefore, you can use this struct to extend the lifetime of its
+dependencies in case you need to store the resolver as a class member.
+*/
+struct QQmlJSTypeResolverDependencies
+{
+ std::shared_ptr<QQmlJSImporter> importer;
+ std::shared_ptr<QQmlJSResourceFileMapper> mapper;
+};
+
QT_END_NAMESPACE
#endif // QQMLJSTYPERESOLVER_P_H
diff --git a/src/qmlcompiler/qqmljsutils.cpp b/src/qmlcompiler/qqmljsutils.cpp
index 410f6b0d14..6de8741f1b 100644
--- a/src/qmlcompiler/qqmljsutils.cpp
+++ b/src/qmlcompiler/qqmljsutils.cpp
@@ -80,7 +80,7 @@ QQmlJSUtils::ResolvedAlias QQmlJSUtils::resolveAlias(const QQmlJSTypeResolver *t
{
return ::resolveAlias(
[&](const QString &id, const QQmlJSScope::ConstPtr &referrer) {
- return typeResolver->scopeForId(id, referrer);
+ return typeResolver->typeForId(referrer, id);
},
property, owner, visitor);
}
@@ -97,7 +97,7 @@ QQmlJSUtils::ResolvedAlias QQmlJSUtils::resolveAlias(const QQmlJSScopesById &idS
property, owner, visitor);
}
-std::optional<FixSuggestion> QQmlJSUtils::didYouMean(const QString &userInput,
+std::optional<QQmlJSFixSuggestion> QQmlJSUtils::didYouMean(const QString &userInput,
QStringList candidates,
QQmlJS::SourceLocation location)
{
@@ -144,10 +144,12 @@ std::optional<FixSuggestion> QQmlJSUtils::didYouMean(const QString &userInput,
}
if (shortestDistance
- < std::min(std::max(userInput.size() / 2, qsizetype(3)), userInput.size())) {
- return FixSuggestion { { FixSuggestion::Fix {
- u"Did you mean \"%1\"?"_s.arg(shortestDistanceWord), location,
- shortestDistanceWord } } };
+ < std::min(std::max(userInput.size() / 2, qsizetype(3)), userInput.size())) {
+ return QQmlJSFixSuggestion {
+ u"Did you mean \"%1\"?"_s.arg(shortestDistanceWord),
+ location,
+ shortestDistanceWord
+ };
} else {
return {};
}
@@ -202,24 +204,16 @@ QQmlJSUtils::sourceDirectoryPath(const QQmlJSImporter *importer, const QString &
Utility method that checks if one of the registers is var, and the other can be
efficiently compared to it
*/
-bool canStrictlyCompareWithVar(const QQmlJSTypeResolver *typeResolver,
- const QQmlJSRegisterContent &lhsContent,
- const QQmlJSRegisterContent &rhsContent)
+bool canStrictlyCompareWithVar(
+ const QQmlJSTypeResolver *typeResolver, const QQmlJSScope::ConstPtr &lhsType,
+ const QQmlJSScope::ConstPtr &rhsType)
{
Q_ASSERT(typeResolver);
- const auto varType = typeResolver->varType();
- const auto nullType = typeResolver->nullType();
- const auto voidType = typeResolver->voidType();
-
- // Use containedType() because nullptr is not a stored type.
- const auto lhsType = typeResolver->containedType(lhsContent);
- const auto rhsType = typeResolver->containedType(rhsContent);
-
- return (typeResolver->equals(lhsType, varType)
- && (typeResolver->equals(rhsType, nullType) || typeResolver->equals(rhsType, voidType)))
- || (typeResolver->equals(rhsType, varType)
- && (typeResolver->equals(lhsType, nullType)
- || typeResolver->equals(lhsType, voidType)));
+
+ const QQmlJSScope::ConstPtr varType = typeResolver->varType();
+ const bool leftIsVar = typeResolver->equals(lhsType, varType);
+ const bool righttIsVar = typeResolver->equals(rhsType, varType);
+ return leftIsVar != righttIsVar;
}
/*! \internal
@@ -227,13 +221,11 @@ bool canStrictlyCompareWithVar(const QQmlJSTypeResolver *typeResolver,
Utility method that checks if one of the registers is qobject, and the other can be
efficiently compared to it
*/
-bool canCompareWithQObject(const QQmlJSTypeResolver *typeResolver,
- const QQmlJSRegisterContent &lhsContent,
- const QQmlJSRegisterContent &rhsContent)
+bool canCompareWithQObject(
+ const QQmlJSTypeResolver *typeResolver, const QQmlJSScope::ConstPtr &lhsType,
+ const QQmlJSScope::ConstPtr &rhsType)
{
Q_ASSERT(typeResolver);
- const auto lhsType = typeResolver->containedType(lhsContent);
- const auto rhsType = typeResolver->containedType(rhsContent);
return (lhsType->isReferenceType()
&& (rhsType->isReferenceType()
|| typeResolver->equals(rhsType, typeResolver->nullType())))
@@ -242,4 +234,18 @@ bool canCompareWithQObject(const QQmlJSTypeResolver *typeResolver,
|| typeResolver->equals(lhsType, typeResolver->nullType())));
}
+/*! \internal
+
+ Utility method that checks if both sides are QUrl type. In future, that might be extended to
+ support comparison with other types i.e QUrl vs string
+*/
+bool canCompareWithQUrl(
+ const QQmlJSTypeResolver *typeResolver, const QQmlJSScope::ConstPtr &lhsType,
+ const QQmlJSScope::ConstPtr &rhsType)
+{
+ Q_ASSERT(typeResolver);
+ return typeResolver->equals(lhsType, typeResolver->urlType())
+ && typeResolver->equals(rhsType, typeResolver->urlType());
+}
+
QT_END_NAMESPACE
diff --git a/src/qmlcompiler/qqmljsutils_p.h b/src/qmlcompiler/qqmljsutils_p.h
index 15425d0cab..c23399e9ae 100644
--- a/src/qmlcompiler/qqmljsutils_p.h
+++ b/src/qmlcompiler/qqmljsutils_p.h
@@ -14,17 +14,20 @@
//
// We mean it.
-#include <private/qtqmlcompilerexports_p.h>
+#include <qtqmlcompilerexports.h>
#include "qqmljslogger_p.h"
#include "qqmljsregistercontent_p.h"
#include "qqmljsscope_p.h"
#include "qqmljsmetatypes_p.h"
+#include <QtCore/qdir.h>
#include <QtCore/qstack.h>
#include <QtCore/qstring.h>
-#include <QtCore/qstringview.h>
#include <QtCore/qstringbuilder.h>
+#include <QtCore/qstringview.h>
+
+#include <QtQml/private/qqmlsignalnames_p.h>
#include <private/qduplicatetracker_p.h>
#include <optional>
@@ -64,7 +67,7 @@ static auto getQQmlJSScopeFromSmartPtr(const From &p) -> decltype(p.get())
class QQmlJSTypeResolver;
class QQmlJSScopesById;
-struct Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSUtils
+struct Q_QMLCOMPILER_EXPORT QQmlJSUtils
{
/*! \internal
Returns escaped version of \a s. This function is mostly useful for code
@@ -73,7 +76,10 @@ struct Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSUtils
static QString escapeString(QString s)
{
using namespace Qt::StringLiterals;
- return s.replace(u'\\', u"\\\\"_s).replace(u'"', u"\\\""_s).replace(u'\n', u"\\n"_s);
+ return s.replace('\\'_L1, "\\\\"_L1)
+ .replace('"'_L1, "\\\""_L1)
+ .replace('\n'_L1, "\\n"_L1)
+ .replace('?'_L1, "\\?"_L1);
}
/*! \internal
@@ -99,26 +105,6 @@ struct Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSUtils
return type;
}
- /*! \internal
- Returns a signal name from \a handlerName string.
- */
- static std::optional<QString> signalName(QStringView handlerName)
- {
- if (handlerName.startsWith(u"on") && handlerName.size() > 2) {
- QString signal = handlerName.mid(2).toString();
- for (int i = 0; i < signal.size(); ++i) {
- QChar &ch = signal[i];
- if (ch.isLower())
- return {};
- if (ch.isUpper()) {
- ch = ch.toLower();
- return signal;
- }
- }
- }
- return {};
- }
-
static std::optional<QQmlJSMetaProperty>
changeHandlerProperty(const QQmlJSScope::ConstPtr &scope, QStringView signalName)
{
@@ -134,6 +120,21 @@ struct Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSUtils
return {};
}
+ static std::optional<QQmlJSMetaProperty>
+ propertyFromChangedHandler(const QQmlJSScope::ConstPtr &scope, QStringView changedHandler)
+ {
+ auto signalName = QQmlSignalNames::changedHandlerNameToPropertyName(changedHandler);
+ if (!signalName)
+ return {};
+
+ auto p = scope->property(*signalName);
+ const bool isBindable = !p.bindable().isEmpty();
+ const bool canNotify = !p.notify().isEmpty();
+ if (p.isValid() && (isBindable || canNotify))
+ return p;
+ return {};
+ }
+
static bool hasCompositeBase(const QQmlJSScope::ConstPtr &scope)
{
if (!scope)
@@ -141,7 +142,7 @@ struct Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSUtils
const auto base = scope->baseType();
if (!base)
return false;
- return base->isComposite() && base->scopeType() == QQmlJSScope::QMLScope;
+ return base->isComposite() && base->scopeType() == QQmlSA::ScopeType::QMLScope;
}
enum PropertyAccessor {
@@ -201,7 +202,7 @@ struct Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSUtils
const AliasResolutionVisitor &visitor);
template<typename QQmlJSScopePtr, typename Action>
- static bool searchBaseAndExtensionTypes(QQmlJSScopePtr type, const Action &check)
+ static bool searchBaseAndExtensionTypes(const QQmlJSScopePtr &type, const Action &check)
{
if (!type)
return false;
@@ -359,21 +360,40 @@ struct Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSUtils
act(begin->scope, begin->extensionSpecifier);
}
- static std::optional<FixSuggestion> didYouMean(const QString &userInput,
+ static std::optional<QQmlJSFixSuggestion> didYouMean(const QString &userInput,
QStringList candidates,
QQmlJS::SourceLocation location);
static std::variant<QString, QQmlJS::DiagnosticMessage>
sourceDirectoryPath(const QQmlJSImporter *importer, const QString &buildDirectoryPath);
+
+ template <typename Container>
+ static void deduplicate(Container &container)
+ {
+ std::sort(container.begin(), container.end());
+ auto erase = std::unique(container.begin(), container.end());
+ container.erase(erase, container.end());
+ }
+
+ static QStringList cleanPaths(QStringList &&paths)
+ {
+ for (QString &path : paths)
+ path = QDir::cleanPath(path);
+ return std::move(paths);
+ }
};
-bool Q_QMLCOMPILER_PRIVATE_EXPORT canStrictlyCompareWithVar(
- const QQmlJSTypeResolver *typeResolver, const QQmlJSRegisterContent &lhsContent,
- const QQmlJSRegisterContent &rhsContent);
+bool Q_QMLCOMPILER_EXPORT canStrictlyCompareWithVar(
+ const QQmlJSTypeResolver *typeResolver, const QQmlJSScope::ConstPtr &lhsType,
+ const QQmlJSScope::ConstPtr &rhsType);
+
+bool Q_QMLCOMPILER_EXPORT canCompareWithQObject(
+ const QQmlJSTypeResolver *typeResolver, const QQmlJSScope::ConstPtr &lhsType,
+ const QQmlJSScope::ConstPtr &rhsType);
-bool Q_QMLCOMPILER_PRIVATE_EXPORT canCompareWithQObject(const QQmlJSTypeResolver *typeResolver,
- const QQmlJSRegisterContent &lhsContent,
- const QQmlJSRegisterContent &rhsContent);
+bool Q_QMLCOMPILER_EXPORT canCompareWithQUrl(
+ const QQmlJSTypeResolver *typeResolver, const QQmlJSScope::ConstPtr &lhsType,
+ const QQmlJSScope::ConstPtr &rhsType);
QT_END_NAMESPACE
diff --git a/src/qmlcompiler/qqmljsvaluetypefromstringcheck.cpp b/src/qmlcompiler/qqmljsvaluetypefromstringcheck.cpp
new file mode 100644
index 0000000000..40ca97e579
--- /dev/null
+++ b/src/qmlcompiler/qqmljsvaluetypefromstringcheck.cpp
@@ -0,0 +1,63 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "qqmljsvaluetypefromstringcheck_p.h"
+#include <private/qqmlstringconverters_p.h>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+/*!
+\internal
+\class ValueTypeFromStringError
+
+Helps to differentiate between three states:
+\list
+\li a value type was constructed by a string in a deprecated way, e.g. "50x70" for a QSizeF
+\li a value type was constructed by an invalid string in a deprecated way, e.g. "50,70" for a QSizeF
+\li a value type was constructed by a string in a non-deprecated way.
+\endlist
+*/
+
+/*!
+\internal
+Checks if known value types are being constructed in a deprecated way, and constructs the code for
+the fix-it.
+*/
+QQmlJSStructuredTypeError QQmlJSValueTypeFromStringCheck::hasError(const QString &typeName,
+ const QString &value)
+{
+ if (typeName == u"QPointF" || typeName == u"QPoint") {
+ std::array<double, 2> numbers;
+ if (!QQmlStringConverters::isValidNumberString<2, u','>(value, &numbers))
+ return QQmlJSStructuredTypeError::withInvalidString();
+
+ const auto result = QQmlJSStructuredTypeError::fromSuggestedString(
+ u"({ x: %1, y: %2 })"_s.arg(numbers[0]).arg(numbers[1]));
+ return result;
+ } else if (typeName == u"QSizeF" || typeName == u"QSize") {
+ std::array<double, 2> numbers;
+ if (!QQmlStringConverters::isValidNumberString<2, u'x'>(value, &numbers))
+ return QQmlJSStructuredTypeError::withInvalidString();
+
+ const auto result = QQmlJSStructuredTypeError::fromSuggestedString(
+ u"({ width: %1, height: %2 })"_s.arg(numbers[0]).arg(numbers[1]));
+ return result;
+ } else if (typeName == u"QRectF" || typeName == u"QRect") {
+ std::array<double, 4> numbers;
+ if (!QQmlStringConverters::isValidNumberString<4, u',', u',', u'x'>(value, &numbers))
+ return QQmlJSStructuredTypeError::withInvalidString();
+
+ const auto result = QQmlJSStructuredTypeError::fromSuggestedString(
+ u"({ x: %1, y: %2, width: %3, height: %4 })"_s.arg(numbers[0])
+ .arg(numbers[1])
+ .arg(numbers[2])
+ .arg(numbers[3]));
+ return result;
+ }
+
+ return QQmlJSStructuredTypeError::withValidString();
+}
+
+QT_END_NAMESPACE
diff --git a/src/qmlcompiler/qqmljsvaluetypefromstringcheck_p.h b/src/qmlcompiler/qqmljsvaluetypefromstringcheck_p.h
new file mode 100644
index 0000000000..227a75003f
--- /dev/null
+++ b/src/qmlcompiler/qqmljsvaluetypefromstringcheck_p.h
@@ -0,0 +1,48 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef QQMLJSVALUETYPEFROMSTRINGCHECK_H
+#define QQMLJSVALUETYPEFROMSTRINGCHECK_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qstring.h>
+
+#include <qtqmlcompilerexports.h>
+
+QT_BEGIN_NAMESPACE
+
+struct Q_QMLCOMPILER_EXPORT QQmlJSStructuredTypeError
+{
+ QString code;
+ bool constructedFromInvalidString = false;
+
+ bool isValid() const { return !code.isEmpty() || constructedFromInvalidString; }
+ static QQmlJSStructuredTypeError withInvalidString() { return { QString(), true }; }
+ static QQmlJSStructuredTypeError withValidString() { return { QString(), false }; }
+ static QQmlJSStructuredTypeError fromSuggestedString(const QString &enhancedString)
+ {
+ return { enhancedString, false };
+ }
+};
+
+
+class Q_QMLCOMPILER_EXPORT QQmlJSValueTypeFromStringCheck
+{
+public:
+ static QQmlJSStructuredTypeError hasError(const QString &typeName, const QString &value);
+};
+
+QT_END_NAMESPACE
+
+#endif // QQMLJSVALUETYPEFROMSTRINGCHECK_H
diff --git a/src/qmlcompiler/qqmlsa.cpp b/src/qmlcompiler/qqmlsa.cpp
index 0b6e88ef7c..d2d0e6a74e 100644
--- a/src/qmlcompiler/qqmlsa.cpp
+++ b/src/qmlcompiler/qqmlsa.cpp
@@ -1,15 +1,21 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+#include "qqmlsa.h"
#include "qqmlsa_p.h"
+#include "qqmlsasourcelocation.h"
#include "qqmljsscope_p.h"
#include "qqmljslogger_p.h"
#include "qqmljstyperesolver_p.h"
#include "qqmljsimportvisitor_p.h"
#include "qqmljsutils_p.h"
+#include "qdeferredpointer_p.h"
+
+#include <QtQmlCompiler/private/qqmlsasourcelocation_p.h>
#include <memory>
+#include <new>
QT_BEGIN_NAMESPACE
@@ -17,44 +23,1043 @@ using namespace Qt::StringLiterals;
namespace QQmlSA {
+static_assert(QQmlJSScope::sizeofQQmlSAElement() == sizeof(Element));
+
+/*!
+ \namespace QQmlSA
+ \inmodule QtQmlCompiler
+
+ \brief Provides tools for static analysis on QML programs.
+ */
+
+/*!
+ \class QQmlSA::Binding::Bindings
+ \inmodule QtQmlCompiler
+
+ \brief Holds multiple property name to property binding associations.
+ */
+
+Binding::Bindings::Bindings() : d_ptr{ new BindingsPrivate{ this } } { }
+
+BindingsPrivate::BindingsPrivate(QQmlSA::Binding::Bindings *interface) : q_ptr{ interface } { }
+
+Binding::Bindings::Bindings(const Bindings &other)
+ : d_ptr{ new BindingsPrivate{ this, *other.d_func() } }
+{
+}
+
+Binding::Bindings::~Bindings() = default;
+
+BindingsPrivate::BindingsPrivate(QQmlSA::Binding::Bindings *interface, const BindingsPrivate &other)
+ : m_bindings{ other.m_bindings.begin(), other.m_bindings.end() }, q_ptr{ interface }
+{
+}
+
+BindingsPrivate::BindingsPrivate(QQmlSA::Binding::Bindings *interface, BindingsPrivate &&other)
+ : m_bindings{ std::move(other.m_bindings) }, q_ptr{ interface }
+{
+}
+
+/*!
+ Returns an iterator to the beginning of the bindings.
+ */
+QMultiHash<QString, Binding>::const_iterator Binding::Bindings::constBegin() const
+{
+ Q_D(const Bindings);
+ return d->constBegin();
+}
+
+QMultiHash<QString, Binding>::const_iterator BindingsPrivate::constBegin() const
+{
+ return m_bindings.constBegin();
+}
+
+/*!
+ Returns an iterator to the end of the bindings.
+ */
+QMultiHash<QString, Binding>::const_iterator Binding::Bindings::constEnd() const
+{
+ Q_D(const Bindings);
+ return d->constEnd();
+}
+
+QMultiHash<QString, Binding>::const_iterator BindingsPrivate::constEnd() const
+{
+ return m_bindings.constEnd();
+}
+
+/*!
+ \class QQmlSA::Binding
+ \inmodule QtQmlCompiler
+
+ \brief Represents a single QML property binding for a specific type.
+ */
+
+Binding::Binding() : d_ptr{ new BindingPrivate{ this } } { }
+
+BindingPrivate::BindingPrivate(Binding *interface) : q_ptr{ interface } { }
+
+Binding::Binding(const Binding &other) : d_ptr{ new BindingPrivate{ this, *other.d_func() } } { }
+
+Binding::Binding(Binding &&other) noexcept
+ : d_ptr{ new BindingPrivate{ this, *other.d_func() } } { }
+
+Binding &Binding::operator=(const Binding &other)
+{
+ if (*this == other)
+ return *this;
+
+ d_func()->m_binding = other.d_func()->m_binding;
+ d_func()->q_ptr = this;
+ return *this;
+}
+
+Binding &Binding::operator=(Binding &&other) noexcept
+{
+ if (*this == other)
+ return *this;
+
+ d_func()->m_binding = std::move(other.d_func()->m_binding);
+ d_func()->q_ptr = this;
+ return *this;
+}
+
+Binding::~Binding() = default;
+
+bool Binding::operatorEqualsImpl(const Binding &lhs, const Binding &rhs)
+{
+ return lhs.d_func()->m_binding == rhs.d_func()->m_binding;
+}
+
+BindingPrivate::BindingPrivate(Binding *interface, const BindingPrivate &other)
+ : m_binding{ other.m_binding }, q_ptr{ interface }
+{
+}
+
+QQmlSA::Binding BindingPrivate::createBinding(const QQmlJSMetaPropertyBinding &binding)
+{
+ QQmlSA::Binding saBinding;
+ saBinding.d_func()->m_binding = binding;
+ return saBinding;
+}
+
+QQmlJSMetaPropertyBinding BindingPrivate::binding(QQmlSA::Binding &binding)
+{
+ return binding.d_func()->m_binding;
+}
+
+const QQmlJSMetaPropertyBinding BindingPrivate::binding(const QQmlSA::Binding &binding)
+{
+ return binding.d_func()->m_binding;
+}
+
+/*!
+ Returns the type of the property of this binding if it is a group property,
+ otherwise returns an invalid Element.
+ */
+Element Binding::groupType() const
+{
+ return QQmlJSScope::createQQmlSAElement(BindingPrivate::binding(*this).groupType());
+}
+
+QQmlSA::BindingType Binding::bindingType() const
+{
+ return BindingPrivate::binding(*this).bindingType();
+}
+
+/*!
+ Returns the associated string literal if the content type of this binding is
+ StringLiteral, otherwise returns an empty string.
+ */
+QString Binding::stringValue() const
+{
+ return BindingPrivate::binding(*this).stringValue();
+}
+
+/*!
+ Returns the name of the property bound with this binding.
+ */
+QString Binding::propertyName() const
+{
+ return BindingPrivate::binding(*this).propertyName();
+}
+
+/*!
+ Returns the attached type if the content type of this binding is
+ AttachedProperty, otherwise returns an invalid Element.
+ */
+Element Binding::attachingType() const
+{
+ return QQmlJSScope::createQQmlSAElement(BindingPrivate::binding(*this).attachingType());
+}
+
+/*!
+ Returns the location in the QML code where this binding is defined.
+ */
+QQmlSA::SourceLocation Binding::sourceLocation() const
+{
+ return QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
+ BindingPrivate::binding(*this).sourceLocation());
+}
+
+/*!
+ Returns the associated number if the content type of this binding is
+ NumberLiteral, otherwise returns 0.
+ */
+double Binding::numberValue() const
+{
+ return BindingPrivate::binding(*this).numberValue();
+}
+
+/*!
+ Returns the kind of the associated script if the content type of this
+ binding is Script, otherwise returns Script_Invalid.
+ */
+QQmlSA::ScriptBindingKind Binding::scriptKind() const
+{
+ return BindingPrivate::binding(*this).scriptKind();
+}
+
+/*!
+ Returns \c true if this binding has an objects, otherwise returns \c false.
+ */
+bool Binding::hasObject() const
+{
+ return BindingPrivate::binding(*this).hasObject();
+}
+
+/*!
+ Returns the type of the associated object if the content type of this
+ binding is Object, otherwise returns an invalid Element.
+ */
+QQmlSA::Element Binding::objectType() const
+{
+ return QQmlJSScope::createQQmlSAElement(BindingPrivate::binding(*this).objectType());
+}
+
+bool Binding::hasUndefinedScriptValue() const
+{
+ const auto &jsBinding = BindingPrivate::binding(*this);
+ return jsBinding.bindingType() == BindingType::Script
+ && jsBinding.scriptValueType() == ScriptValue_Undefined;
+}
+
+/*!
+ Returns \c true if \a bindingType is a literal type, and \c false
+ otherwise. Literal types include strings, booleans, numbers, regular
+ expressions.
+ */
+bool QQmlSA::Binding::isLiteralBinding(QQmlSA::BindingType bindingType)
+{
+ return QQmlJSMetaPropertyBinding::isLiteralBinding(bindingType);
+}
+
+QQmlSA::Method::Methods::Methods() : d_ptr{ new MethodsPrivate{ this } } { }
+
+QQmlSA::Method::Methods::Methods(const Methods &other)
+ : d_ptr{ new MethodsPrivate{ this, *other.d_func() } }
+{
+}
+
+QQmlSA::Method::Methods::~Methods() = default;
+
+/*!
+ Returns an iterator to the beginning of the methods.
+ */
+QMultiHash<QString, Method>::const_iterator Method::Methods::constBegin() const
+{
+ Q_D(const Methods);
+ return d->constBegin();
+}
+
+QMultiHash<QString, Method>::const_iterator MethodsPrivate::constBegin() const
+{
+ return m_methods.constBegin();
+}
+
+/*!
+ Returns an iterator to the end of the methods.
+ */
+QMultiHash<QString, Method>::const_iterator Method::Methods::constEnd() const
+{
+ Q_D(const Methods);
+ return d->constEnd();
+}
+QMultiHash<QString, Method>::const_iterator MethodsPrivate::constEnd() const
+{
+ return m_methods.constEnd();
+}
+
+MethodsPrivate::MethodsPrivate(QQmlSA::Method::Methods *interface) : q_ptr{ interface } { }
+
+MethodsPrivate::MethodsPrivate(QQmlSA::Method::Methods *interface, const MethodsPrivate &other)
+ : m_methods{ other.m_methods }, q_ptr{ interface }
+{
+}
+
+MethodsPrivate::MethodsPrivate(QQmlSA::Method::Methods *interface, MethodsPrivate &&other)
+ : m_methods{ std::move(other.m_methods) }, q_ptr{ interface }
+{
+}
+
+MethodPrivate::MethodPrivate(Method *interface) : q_ptr{ interface } { }
+
+MethodPrivate::MethodPrivate(Method *interface, const MethodPrivate &other)
+ : m_method{ other.m_method }, q_ptr{ interface }
+{
+}
+
+QString MethodPrivate::methodName() const
+{
+ return m_method.methodName();
+}
+
+QQmlSA::SourceLocation MethodPrivate::sourceLocation() const
+{
+ return QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(m_method.sourceLocation());
+}
+
+MethodType MethodPrivate::methodType() const
+{
+ return m_method.methodType();
+}
+
+/*!
+ \class QQmlSA::Method
+ \inmodule QtQmlCompiler
+
+ \brief Represents a QML method.
+ */
+
+Method::Method() : d_ptr{ new MethodPrivate{ this } } { }
+
+Method::Method(const Method &other) : d_ptr{ new MethodPrivate{ this, *other.d_func() } } { }
+
+Method::Method(Method &&other) noexcept
+ : d_ptr{ new MethodPrivate{ this, std::move(*other.d_func()) } }
+{
+}
+
+Method &Method::operator=(const Method &other)
+{
+ if (*this == other)
+ return *this;
+
+ d_func()->m_method = other.d_func()->m_method;
+ d_func()->q_ptr = this;
+ return *this;
+}
+
+Method &Method::operator=(Method &&other) noexcept
+{
+ if (*this == other)
+ return *this;
+
+ d_func()->m_method = std::move(other.d_func()->m_method);
+ d_func()->q_ptr = this;
+ return *this;
+}
+
+Method::~Method() = default;
+
+/*!
+ Returns the name of the this method.
+ */
+QString Method::methodName() const
+{
+ Q_D(const Method);
+ return d->methodName();
+}
+
+/*!
+ Returns the type of this method. For example, Signal, Slot, Method or
+ StaticMethod.
+ */
+MethodType Method::methodType() const
+{
+ Q_D(const Method);
+ return d->methodType();
+}
+
+/*!
+ Returns the location in the QML code where this method is defined.
+ */
+QQmlSA::SourceLocation Method::sourceLocation() const
+{
+ Q_D(const Method);
+ return d->sourceLocation();
+}
+
+bool Method::operatorEqualsImpl(const Method &lhs, const Method &rhs)
+{
+ return lhs.d_func()->m_method == rhs.d_func()->m_method;
+}
+
+QQmlSA::Method MethodPrivate::createMethod(const QQmlJSMetaMethod &jsMethod)
+{
+ QQmlSA::Method saMethod;
+ auto &wrappedMethod = saMethod.d_func()->m_method;
+ wrappedMethod = jsMethod;
+ return saMethod;
+}
+
+QQmlSA::Method::Methods
+MethodsPrivate::createMethods(const QMultiHash<QString, QQmlJSMetaMethod> &hash)
+{
+ QMultiHash<QString, QQmlSA::Method> saMethods;
+ for (const auto &[key, value] : hash.asKeyValueRange()) {
+ saMethods.insert(key, MethodPrivate::createMethod(value));
+ }
+
+ QQmlSA::Method::Methods methods;
+ methods.d_func()->m_methods = std::move(saMethods);
+ return methods;
+}
+
+QQmlJSMetaMethod MethodPrivate::method(const QQmlSA::Method &method)
+{
+ return method.d_func()->m_method;
+}
+
+PropertyPrivate::PropertyPrivate(Property *interface) : q_ptr{ interface } { }
+
+PropertyPrivate::PropertyPrivate(Property *interface, const PropertyPrivate &other)
+ : m_property{ other.m_property }, q_ptr{ interface }
+{
+}
+
+PropertyPrivate::PropertyPrivate(Property *interface, PropertyPrivate &&other)
+ : m_property{ std::move(other.m_property) }, q_ptr{ interface }
+{
+}
+
+QString PropertyPrivate::typeName() const
+{
+ return m_property.typeName();
+}
+
+bool PropertyPrivate::isValid() const
+{
+ return m_property.isValid();
+}
+
+/*!
+ Returns whether this property is readonly. Properties defined in QML are readonly when their
+ definition has the 'readonly' keyword. Properties defined in C++ are readonly when they do not
+ have a WRITE accessor function.
+ */
+bool PropertyPrivate::isReadonly() const
+{
+ return !m_property.isWritable();
+}
+
+/*!
+ Returns the type that this property was defined with.
+ */
+QQmlSA::Element PropertyPrivate::type() const
+{
+ return QQmlJSScope::createQQmlSAElement(m_property.type());
+}
+
+QQmlJSMetaProperty PropertyPrivate::property(const QQmlSA::Property &property)
+{
+ return property.d_func()->m_property;
+}
+
+QQmlSA::Property PropertyPrivate::createProperty(const QQmlJSMetaProperty &property)
+{
+ QQmlSA::Property saProperty;
+ auto &wrappedProperty = saProperty.d_func()->m_property;
+ wrappedProperty = property;
+ return saProperty;
+}
+
+/*!
+ \class QQmlSA::Property
+ \inmodule QtQmlCompiler
+
+ \brief Represents a QML property.
+ */
+
+Property::Property() : d_ptr{ new PropertyPrivate{ this } } { }
+
+Property::Property(const Property &other)
+ : d_ptr{ new PropertyPrivate{ this, *other.d_func() } } { }
+
+Property::Property(Property &&other) noexcept
+ : d_ptr{ new PropertyPrivate{ this, std::move(*other.d_func()) } }
+{
+}
+
+Property &Property::operator=(const Property &other)
+{
+ if (*this == other)
+ return *this;
+
+ d_func()->m_property = other.d_func()->m_property;
+ d_func()->q_ptr = this;
+ return *this;
+}
+
+Property &Property::operator=(Property &&other) noexcept
+{
+ if (*this == other)
+ return *this;
+
+ d_func()->m_property = std::move(other.d_func()->m_property);
+ d_func()->q_ptr = this;
+ return *this;
+}
+
+Property::~Property() = default;
+
+/*!
+ Returns the name of the type of this property.
+ */
+QString Property::typeName() const
+{
+ Q_D(const Property);
+ return d->typeName();
+}
+
+bool Property::isValid() const
+{
+ Q_D(const Property);
+ return d->isValid();
+}
+
+bool Property::isReadonly() const
+{
+ Q_D(const Property);
+ return d->isReadonly();
+}
+
+QQmlSA::Element Property::type() const
+{
+ Q_D(const Property);
+ return d->type();
+}
+
+
+bool Property::operatorEqualsImpl(const Property &lhs, const Property &rhs)
+{
+ return lhs.d_func()->m_property == rhs.d_func()->m_property;
+}
+
+/*!
+ \class QQmlSA::Element
+ \inmodule QtQmlCompiler
+
+ \brief Represents a QML type.
+ */
+
+Element::Element()
+{
+ new (m_data) QQmlJSScope::ConstPtr();
+}
+
+Element::Element(const Element &other)
+{
+ new (m_data) QQmlJSScope::ConstPtr(QQmlJSScope::scope(other));
+}
+
+Element &Element::operator=(const Element &other)
+{
+ if (this == &other)
+ return *this;
+
+ *reinterpret_cast<QQmlJSScope::ConstPtr *>(m_data) = QQmlJSScope::scope(other);
+ return *this;
+}
+
+Element::~Element()
+{
+ (*reinterpret_cast<QQmlJSScope::ConstPtr *>(m_data)).QQmlJSScope::ConstPtr::~ConstPtr();
+}
+
+/*!
+ Returns the type of Element's scope.
+ */
+QQmlJSScope::ScopeType Element::scopeType() const
+{
+ return QQmlJSScope::scope(*this)->scopeType();
+}
+
+/*!
+ Returns the Element this Element derives from.
+ */
+Element Element::baseType() const
+{
+ return QQmlJSScope::createQQmlSAElement(QQmlJSScope::scope(*this)->baseType());
+}
+
+/*!
+ Returns the name of the Element this Element derives from.
+ */
+QString Element::baseTypeName() const
+{
+ return QQmlJSScope::prettyName(QQmlJSScope::scope(*this)->baseTypeName());
+}
+
+/*!
+ Returns the Element that encloses this Element.
+ */
+Element Element::parentScope() const
+{
+ return QQmlJSScope::createQQmlSAElement(QQmlJSScope::scope(*this)->parentScope());
+}
+
+/*!
+ Returns whether this Element inherits from \a element.
+ */
+bool Element::inherits(const Element &element) const
+{
+ return QQmlJSScope::scope(*this)->inherits(QQmlJSScope::scope(element));
+}
+
+bool Element::isNull() const
+{
+ return QQmlJSScope::scope(*this).isNull();
+}
+
+/*!
+ \internal
+ */
+QString Element::internalId() const
+{
+ return QQmlJSScope::scope(*this)->internalName();
+}
+
+/*!
+ Returns the access semantics of this Element. For example, Reference,
+ Value or Sequence.
+ */
+AccessSemantics Element::accessSemantics() const
+{
+ return QQmlJSScope::scope(*this)->accessSemantics();
+}
+
+/*!
+ Returns true for objects defined from Qml, and false for objects declared from C++.
+ */
+bool QQmlSA::Element::isComposite() const
+{
+ return QQmlJSScope::scope(*this)->isComposite();
+}
+
+/*!
+ Returns whether this Element has a property with the name \a propertyName.
+ */
+bool Element::hasProperty(const QString &propertyName) const
+{
+ return QQmlJSScope::scope(*this)->hasProperty(propertyName);
+}
+
+/*!
+ Returns whether this Element defines a property with the name \a propertyName
+ which is not defined on its base or extension objects.
+ */
+bool Element::hasOwnProperty(const QString &propertyName) const
+{
+ return QQmlJSScope::scope(*this)->hasOwnProperty(propertyName);
+}
+
+/*!
+ Returns the property with the name \a propertyName if it is found in this
+ Element or its base and extension objects, otherwise returns an invalid property.
+ */
+QQmlSA::Property Element::property(const QString &propertyName) const
+{
+ return PropertyPrivate::createProperty(QQmlJSScope::scope(*this)->property(propertyName));
+}
+
+/*!
+ Returns whether the property with the name \a propertyName resolved on this
+ Element is required. Returns false if the the property couldn't be found.
+ */
+bool Element::isPropertyRequired(const QString &propertyName) const
+{
+ return QQmlJSScope::scope(*this)->isPropertyRequired(propertyName);
+}
+
+/*!
+ Returns the name of the default property of this Element. If it doesn't
+ have one, returns an empty string.
+ */
+QString Element::defaultPropertyName() const
+{
+ return QQmlJSScope::scope(*this)->defaultPropertyName();
+}
+
+/*!
+ Returns whether this Element has a method with the name \a methodName.
+ */
+bool Element::hasMethod(const QString &methodName) const
+{
+ return QQmlJSScope::scope(*this)->hasMethod(methodName);
+}
+
+/*!
+ \class QQmlSA::Method::Methods
+ \inmodule QtQmlCompiler
+
+ \brief Holds multiple method name to method associations.
+ */
+
+/*!
+ Returns this Elements's methods, which are not defined on its base or
+ extension objects.
+ */
+Method::Methods Element::ownMethods() const
+{
+ return MethodsPrivate::createMethods(QQmlJSScope::scope(*this)->ownMethods());
+}
+
+/*!
+ Returns the location in the QML code where this Element is defined.
+ */
+QQmlSA::SourceLocation Element::sourceLocation() const
+{
+ return QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
+ QQmlJSScope::scope(*this)->sourceLocation());
+}
+
+/*!
+ Returns the file path of the QML code that defines this Element.
+ */
+QString Element::filePath() const
+{
+ return QQmlJSScope::scope(*this)->filePath();
+}
+
+/*!
+ Returns whether this Element has a property binding with the name \a name.
+ */
+bool Element::hasPropertyBindings(const QString &name) const
+{
+ return QQmlJSScope::scope(*this)->hasPropertyBindings(name);
+}
+
+/*!
+ Returns whether this Element has property bindings which are not defined in
+ its base or extension objects and that have name \a propertyName.
+ */
+bool Element::hasOwnPropertyBindings(const QString &propertyName) const
+{
+ return QQmlJSScope::scope(*this)->hasOwnPropertyBindings(propertyName);
+}
+
+/*!
+ Returns this Element's property bindings which are not defined on its base
+ or extension objects.
+ */
+Binding::Bindings Element::ownPropertyBindings() const
+{
+ return BindingsPrivate::createBindings(QQmlJSScope::scope(*this)->ownPropertyBindings());
+}
+
+/*!
+ Returns this Element's property bindings which are not defined on its base
+ or extension objects and that have the name \a propertyName.
+ */
+Binding::Bindings Element::ownPropertyBindings(const QString &propertyName) const
+{
+ return BindingsPrivate::createBindings(
+ QQmlJSScope::scope(*this)->ownPropertyBindings(propertyName));
+}
+
+/*!
+ Returns this Element's property bindings that have the name \a propertyName.
+ */
+QList<Binding> Element::propertyBindings(const QString &propertyName) const
+{
+ const auto &bindings = QQmlJSScope::scope(*this)->propertyBindings(propertyName);
+
+ QList<Binding> saBindings;
+ for (const auto &jsBinding : bindings) {
+ saBindings.push_back(BindingPrivate::createBinding(jsBinding));
+ }
+ return saBindings;
+}
+
+QQmlSA::Binding::Bindings
+BindingsPrivate::createBindings(const QMultiHash<QString, QQmlJSMetaPropertyBinding> &hash)
+{
+ QMultiHash<QString, QQmlSA::Binding> saBindings;
+ for (const auto &[key, value] : hash.asKeyValueRange()) {
+ saBindings.insert(key, BindingPrivate::createBinding(value));
+ }
+
+ QQmlSA::Binding::Bindings bindings;
+ bindings.d_func()->m_bindings = std::move(saBindings);
+ return bindings;
+}
+
+QQmlSA::Binding::Bindings BindingsPrivate::createBindings(
+ QPair<QMultiHash<QString, QQmlJSMetaPropertyBinding>::const_iterator,
+ QMultiHash<QString, QQmlJSMetaPropertyBinding>::const_iterator> iterators)
+{
+ QMultiHash<QString, QQmlSA::Binding> saBindings;
+ for (auto it = iterators.first; it != iterators.second; ++it) {
+ saBindings.insert(it.key(), BindingPrivate::createBinding(it.value()));
+ }
+
+ QQmlSA::Binding::Bindings bindings;
+ bindings.d_func()->m_bindings = std::move(saBindings);
+ return bindings;
+}
+
+Element::operator bool() const
+{
+ return bool(QQmlJSScope::scope(*this));
+}
+
+bool Element::operator!() const
+{
+ return !QQmlJSScope::scope(*this);
+}
+
+/*!
+ Returns the name of this Element.
+ */
+QString Element::name() const
+{
+ if (isNull())
+ return {};
+ return QQmlJSScope::prettyName(QQmlJSScope::scope(*this)->internalName());
+}
+
+bool Element::operatorEqualsImpl(const Element &lhs, const Element &rhs)
+{
+ return QQmlJSScope::scope(lhs) == QQmlJSScope::scope(rhs);
+}
+
+qsizetype Element::qHashImpl(const Element &key, qsizetype seed) noexcept
+{
+ return qHash(QQmlJSScope::scope(key), seed);
+}
+
+/*!
+ \class QQmlSA::GenericPass
+ \inmodule QtQmlCompiler
+
+ \brief The base class for static analysis passes.
+
+ This class contains common functionality used by more specific passses.
+ Custom passes should not directly derive from it, but rather from one of
+ its subclasses.
+ \sa ElementPass, PropertyPass
+ */
+
class GenericPassPrivate {
+ Q_DECLARE_PUBLIC(GenericPass);
+
public:
- const PassManager *manager;
+ GenericPassPrivate(GenericPass *interface, PassManager *manager)
+ : m_manager{ manager }, q_ptr{ interface }
+ {
+ Q_ASSERT(manager);
+ }
+
+private:
+ PassManager *m_manager;
+
+ GenericPass *q_ptr;
};
GenericPass::~GenericPass() = default;
+/*!
+ Creates a generic pass.
+ */
GenericPass::GenericPass(PassManager *manager)
+ : d_ptr{ new GenericPassPrivate{ this, manager } } { }
+
+/*!
+ Emits a warning message \a diagnostic about an issue of type \a id.
+ */
+void GenericPass::emitWarning(QAnyStringView diagnostic, LoggerWarningId id)
{
- Q_ASSERT(manager);
- d = std::make_unique<GenericPassPrivate>();
- d->manager = manager;
+ emitWarning(diagnostic, id, QQmlSA::SourceLocation{});
}
-void GenericPass::emitWarning(QAnyStringView message, LoggerWarningId id,
- QQmlJS::SourceLocation srcLocation)
+/*!
+ Emits warning message \a diagnostic about an issue of type \a id located at
+ \a srcLocation.
+ */
+void GenericPass::emitWarning(QAnyStringView diagnostic, LoggerWarningId id,
+ QQmlSA::SourceLocation srcLocation)
+{
+ Q_D(const GenericPass);
+ PassManagerPrivate::visitor(*d->m_manager)
+ ->logger()
+ ->log(diagnostic.toString(), id,
+ QQmlSA::SourceLocationPrivate::sourceLocation(srcLocation));
+}
+
+/*!
+ Emits a warning message \a diagnostic about an issue of type \a id located at
+ \a srcLocation and with suggested fix \a fix.
+ */
+void GenericPass::emitWarning(QAnyStringView diagnostic, LoggerWarningId id,
+ QQmlSA::SourceLocation srcLocation, const QQmlSA::FixSuggestion &fix)
+{
+ Q_D(const GenericPass);
+ PassManagerPrivate::visitor(*d->m_manager)
+ ->logger()
+ ->log(diagnostic.toString(), id,
+ QQmlSA::SourceLocationPrivate::sourceLocation(srcLocation), true, true,
+ FixSuggestionPrivate::fixSuggestion(fix));
+}
+
+/*!
+ Returns the type corresponding to \a typeName inside the
+ currently analysed file.
+ */
+Element GenericPass::resolveTypeInFileScope(QAnyStringView typeName)
{
- d->manager->m_visitor->logger()->log(message.toString(), id, srcLocation);
+ Q_D(const GenericPass);
+ const auto scope =
+ PassManagerPrivate::visitor(*d->m_manager)->imports().type(typeName.toString()).scope;
+ return QQmlJSScope::createQQmlSAElement(scope);
}
+/*!
+ Returns the attached type corresponding to \a typeName used inside
+ the currently analysed file.
+ */
+Element GenericPass::resolveAttachedInFileScope(QAnyStringView typeName)
+{
+ const auto type = resolveTypeInFileScope(typeName);
+ const auto scope = QQmlJSScope::scope(type);
+
+ if (scope.isNull())
+ return QQmlJSScope::createQQmlSAElement(QQmlJSScope::ConstPtr(nullptr));
+
+ return QQmlJSScope::createQQmlSAElement(scope->attachedType());
+}
+
+/*!
+ Returns the type of \a typeName defined in module \a moduleName.
+ If an attached type and a non-attached type share the same name
+ (for example, \c ListView), the \l Element corresponding to the
+ non-attached type is returned.
+ To obtain the attached type, use \l resolveAttached.
+ */
Element GenericPass::resolveType(QAnyStringView moduleName, QAnyStringView typeName)
{
- auto typeImporter = d->manager->m_visitor->importer();
- auto module = typeImporter->importModule(moduleName.toString());
- return module.type(typeName.toString()).scope;
+ Q_D(const GenericPass);
+ QQmlJSImporter *typeImporter = PassManagerPrivate::visitor(*d->m_manager)->importer();
+ const auto module = typeImporter->importModule(moduleName.toString());
+ const auto scope = module.type(typeName.toString()).scope;
+ return QQmlJSScope::createQQmlSAElement(scope);
+}
+
+/*!
+ Returns the type of the built-in type identified by \a typeName.
+ Built-in types encompass \c{C++} types which the QML engine can handle
+ without any imports (e.g. \l QDateTime and \l QString), global EcmaScript
+ objects like \c Number, as well as the \l {QML Global Object}
+ {global Qt object}.
+ */
+Element GenericPass::resolveBuiltinType(QAnyStringView typeName) const
+{
+ Q_D(const GenericPass);
+ QQmlJSImporter *typeImporter = PassManagerPrivate::visitor(*d->m_manager)->importer();
+ auto typeNameString = typeName.toString();
+ // we have to check both cpp names
+ auto scope = typeImporter->builtinInternalNames().type(typeNameString).scope;
+ if (!scope) {
+ // and qml names (e.g. for bool) - builtinImportHelper is private, so we can't do it in one call
+ auto builtins = typeImporter->importBuiltins();
+ scope = builtins.type(typeNameString).scope;
+ }
+ return QQmlJSScope::createQQmlSAElement(scope);
+}
+
+/*!
+ Returns the attached type of \a typeName defined in module \a moduleName.
+ */
+Element GenericPass::resolveAttached(QAnyStringView moduleName, QAnyStringView typeName)
+{
+ const auto &resolvedType = resolveType(moduleName, typeName);
+ return QQmlJSScope::createQQmlSAElement(QQmlJSScope::scope(resolvedType)->attachedType());
}
-Element GenericPass::resolveLiteralType(const QQmlJSMetaPropertyBinding &binding)
+/*!
+ Returns the element representing the type of literal in \a binding. If the
+ binding does not contain a literal value, a null Element is returned.
+ */
+Element GenericPass::resolveLiteralType(const QQmlSA::Binding &binding)
{
- return binding.literalType(d->manager->m_typeResolver);
+ Q_D(const GenericPass);
+
+ return QQmlJSScope::createQQmlSAElement(BindingPrivate::binding(binding).literalType(
+ PassManagerPrivate::resolver(*d->m_manager)));
}
/*!
- * \brief PassManager::registerElementPass registers ElementPass
+ Returns the element in \a context that has id \a id.
+ */
+Element GenericPass::resolveIdToElement(QAnyStringView id, const Element &context)
+{
+ Q_D(const GenericPass);
+ const auto scope = PassManagerPrivate::visitor(*d->m_manager)
+ ->addressableScopes()
+ .scope(id.toString(), QQmlJSScope::scope(context));
+ return QQmlJSScope::createQQmlSAElement(scope);
+}
+
+/*!
+ Returns the id of \a element in a given \a context.
+ */
+QString GenericPass::resolveElementToId(const Element &element, const Element &context)
+{
+ Q_D(const GenericPass);
+ return PassManagerPrivate::visitor(*d->m_manager)
+ ->addressableScopes()
+ .id(QQmlJSScope::scope(element), QQmlJSScope::scope(context));
+}
+
+/*!
+ Returns the source code located within \a location.
+ */
+QString GenericPass::sourceCode(QQmlSA::SourceLocation location)
+{
+ Q_D(const GenericPass);
+ return PassManagerPrivate::visitor(*d->m_manager)
+ ->logger()
+ ->code()
+ .mid(location.offset(), location.length());
+}
+
+/*!
+ \class QQmlSA::PassManager
+ \inmodule QtQmlCompiler
+
+ \brief Can analyze an element and its children with static analysis passes.
+ */
+
+// explicitly defaulted out-of-line for PIMPL
+PassManager::PassManager() = default;
+PassManager::~PassManager() = default;
+
+/*!
+ Registers a static analysis \a pass to be run on all elements.
+ */
+void PassManager::registerElementPass(std::unique_ptr<ElementPass> pass)
+{
+ Q_D(PassManager);
+ d->registerElementPass(std::move(pass));
+}
+
+/*!
+ \internal
+ \brief PassManager::registerElementPass registers ElementPass
with the pass manager.
\param pass The registered pass. Ownership is transferred to the pass manager.
*/
-void PassManager::registerElementPass(std::unique_ptr<ElementPass> pass)
+void PassManagerPrivate::registerElementPass(std::unique_ptr<ElementPass> pass)
{
m_elementPasses.push_back(std::move(pass));
}
@@ -63,76 +1068,138 @@ enum LookupMode { Register, Lookup };
static QString lookupName(const QQmlSA::Element &element, LookupMode mode = Lookup)
{
QString name;
- if (element.isNull() || element->internalName().isEmpty()) {
+ if (element.isNull() || QQmlJSScope::scope(element)->internalName().isEmpty()) {
// Bail out with an invalid name, this type is so screwed up we can't do anything reasonable
// with it We should have warned about it in another plac
- if (element.isNull() || element->baseType().isNull())
+ if (element.isNull() || element.baseType().isNull())
return u"$INVALID$"_s;
- name = element->baseType()->internalName();
+ name = QQmlJSScope::scope(element.baseType())->internalName();
} else {
- name = element->internalName();
+ name = QQmlJSScope::scope(element)->internalName();
}
const QString filePath =
- (mode == Register || !element->baseType() ? element : element->baseType())->filePath();
+ (mode == Register || !element.baseType() ? element : element.baseType()).filePath();
- if (element->isComposite() && !filePath.endsWith(u".h"))
+ if (QQmlJSScope::scope(element)->isComposite() && !filePath.endsWith(u".h"))
name += u'@' + filePath;
return name;
}
+/*!
+ Registers a static analysis pass for properties. The \a pass will be run on
+ every property matching the \a moduleName, \a typeName and \a propertyName.
+
+ Omitting the \a propertyName will register this pass for all properties
+ matching the \a typeName and \a moduleName.
+
+ Setting \a allowInheritance to \c true means that the filtering on the type
+ also accepts types deriving from \a typeName.
+
+ \a pass is passed as a \c{std::shared_ptr} to allow reusing the same pass
+ on multiple elements:
+ \code
+ auto titleValiadorPass = std::make_shared<TitleValidatorPass>(manager);
+ manager->registerPropertyPass(titleValidatorPass,
+ "QtQuick", "Window", "title");
+ manager->registerPropertyPass(titleValidatorPass,
+ "QtQuick.Controls", "Dialog", "title");
+ \endcode
+
+ \note Running analysis passes on too many items can be expensive. This is
+ why it is generally good to filter down the set of properties of a pass
+ using the \a moduleName, \a typeName and \a propertyName.
+
+ Returns \c true if the pass was successfully added, \c false otherwise.
+ Adding a pass fails when the \l{QQmlSA::Element}{Element} specified by
+ \a moduleName and \a typeName does not exist.
+
+ \sa PropertyPass
+*/
bool PassManager::registerPropertyPass(std::shared_ptr<PropertyPass> pass,
QAnyStringView moduleName, QAnyStringView typeName,
QAnyStringView propertyName, bool allowInheritance)
{
+ Q_D(PassManager);
+ return d->registerPropertyPass(pass, moduleName, typeName, propertyName, allowInheritance);
+}
+
+bool PassManagerPrivate::registerPropertyPass(std::shared_ptr<PropertyPass> pass,
+ QAnyStringView moduleName, QAnyStringView typeName,
+ QAnyStringView propertyName, bool allowInheritance)
+{
+ if (moduleName.isEmpty() != typeName.isEmpty()) {
+ qWarning() << "Both the moduleName and the typeName must be specified "
+ "for the pass to be registered for a specific element.";
+ }
+
QString name;
if (!moduleName.isEmpty() && !typeName.isEmpty()) {
auto typeImporter = m_visitor->importer();
auto module = typeImporter->importModule(moduleName.toString());
- auto element = module.type(typeName.toString()).scope;
+ auto element = QQmlJSScope::createQQmlSAElement(module.type(typeName.toString()).scope);
if (element.isNull())
return false;
name = lookupName(element, Register);
}
- const PassManager::PropertyPassInfo passInfo {
- propertyName.isEmpty() ? QStringList {} : QStringList { propertyName.toString() },
- std::move(pass), allowInheritance
- };
+ const QQmlSA::PropertyPassInfo passInfo{ propertyName.isEmpty()
+ ? QStringList{}
+ : QStringList{ propertyName.toString() },
+ std::move(pass), allowInheritance };
m_propertyPasses.insert({ name, passInfo });
return true;
}
-void PassManager::addBindingSourceLocations(const Element &element, const Element &scope,
- const QString prefix, bool isAttached)
+void PassManagerPrivate::addBindingSourceLocations(const Element &element, const Element &scope,
+ const QString prefix, bool isAttached)
{
const Element &currentScope = scope.isNull() ? element : scope;
- const auto ownBindings = currentScope->ownPropertyBindings();
- for (const auto &binding : ownBindings.values()) {
+ const auto ownBindings = currentScope.ownPropertyBindings();
+ for (const auto &binding : ownBindings) {
switch (binding.bindingType()) {
- case QQmlJSMetaPropertyBinding::GroupProperty:
- addBindingSourceLocations(element, binding.groupType(),
+ case QQmlSA::BindingType::GroupProperty:
+ addBindingSourceLocations(element, Element{ binding.groupType() },
prefix + binding.propertyName() + u'.');
break;
- case QQmlJSMetaPropertyBinding::AttachedProperty:
- addBindingSourceLocations(element, binding.attachingType(),
+ case QQmlSA::BindingType::AttachedProperty:
+ addBindingSourceLocations(element, Element{ binding.attachingType() },
prefix + binding.propertyName() + u'.', true);
break;
default:
- m_bindingsByLocation.insert({ binding.sourceLocation().offset,
- BindingInfo { prefix + binding.propertyName(), binding,
- currentScope, isAttached } });
+ m_bindingsByLocation.insert({ binding.sourceLocation().offset(),
+ BindingInfo{ prefix + binding.propertyName(), binding,
+ currentScope, isAttached } });
- if (binding.bindingType() != QQmlJSMetaPropertyBinding::Script)
+ if (binding.bindingType() != QQmlSA::BindingType::Script)
analyzeBinding(element, QQmlSA::Element(), binding.sourceLocation());
}
}
}
+/*!
+ Runs the element passes over \a root and all its children.
+ */
void PassManager::analyze(const Element &root)
{
+ Q_D(PassManager);
+ d->analyze(root);
+}
+
+static QQmlJS::ConstPtrWrapperIterator childScopesBegin(const Element &element)
+{
+ return QQmlJSScope::scope(element)->childScopesBegin();
+}
+
+static QQmlJS::ConstPtrWrapperIterator childScopesEnd(const Element &element)
+{
+ return QQmlJSScope::scope(element)->childScopesEnd();
+}
+
+void PassManagerPrivate::analyze(const Element &root)
+{
QList<Element> runStack;
runStack.push_back(root);
while (!runStack.isEmpty()) {
@@ -141,33 +1208,33 @@ void PassManager::analyze(const Element &root)
for (auto &elementPass : m_elementPasses)
if (elementPass->shouldRun(element))
elementPass->run(element);
- const auto ownPropertyBindings = element->ownPropertyBindings();
- for (auto it = element->childScopesBegin(); it != element->childScopesEnd(); ++it) {
- if ((*it)->scopeType() == QQmlJSScope::QMLScope)
- runStack.push_back(*it);
+ for (auto it = childScopesBegin(element), end = childScopesEnd(element); it != end; ++it) {
+ if ((*it)->scopeType() == QQmlSA::ScopeType::QMLScope)
+ runStack.push_back(QQmlJSScope::createQQmlSAElement(*it));
}
}
}
-void PassManager::analyzeWrite(const Element &element, QString propertyName, const Element &value,
- const Element &writeScope, QQmlJS::SourceLocation location)
+void PassManagerPrivate::analyzeWrite(const Element &element, QString propertyName,
+ const Element &value, const Element &writeScope,
+ QQmlSA::SourceLocation location)
{
for (PropertyPass *pass : findPropertyUsePasses(element, propertyName))
pass->onWrite(element, propertyName, value, writeScope, location);
}
-void PassManager::analyzeRead(const Element &element, QString propertyName,
- const Element &readScope, QQmlJS::SourceLocation location)
+void PassManagerPrivate::analyzeRead(const Element &element, QString propertyName,
+ const Element &readScope, QQmlSA::SourceLocation location)
{
for (PropertyPass *pass : findPropertyUsePasses(element, propertyName))
pass->onRead(element, propertyName, readScope, location);
}
-void PassManager::analyzeBinding(const Element &element, const QQmlSA::Element &value,
- QQmlJS::SourceLocation location)
+void PassManagerPrivate::analyzeBinding(const Element &element, const QQmlSA::Element &value,
+ QQmlSA::SourceLocation location)
{
- const auto info = m_bindingsByLocation.find(location.offset);
+ const auto info = m_bindingsByLocation.find(location.offset());
// If there's no matching binding that means we're in a nested Ret somewhere inside an
// expression
@@ -175,33 +1242,68 @@ void PassManager::analyzeBinding(const Element &element, const QQmlSA::Element &
return;
const QQmlSA::Element &bindingScope = info->second.bindingScope;
- const QQmlJSMetaPropertyBinding &binding = info->second.binding;
+ const QQmlSA::Binding &binding = info->second.binding;
const QString &propertyName = info->second.fullPropertyName;
for (PropertyPass *pass : findPropertyUsePasses(element, propertyName))
pass->onBinding(element, propertyName, binding, bindingScope, value);
- if (!info->second.isAttached || bindingScope->baseType().isNull())
+ if (!info->second.isAttached || bindingScope.baseType().isNull())
return;
- for (PropertyPass *pass : findPropertyUsePasses(bindingScope->baseType(), propertyName))
+ for (PropertyPass *pass : findPropertyUsePasses(bindingScope.baseType(), propertyName))
pass->onBinding(element, propertyName, binding, bindingScope, value);
}
+/*!
+ Returns \c true if the module named \a module has been imported by the
+ QML to be analyzed, \c false otherwise.
+
+ This can be used to skip registering a pass which is specific to a specific
+ module.
+
+ \code
+ if (passManager->hasImportedModule("QtPositioning"))
+ passManager->registerElementPass(
+ std::make_unique<PositioningPass>(passManager)
+ );
+ \endcode
+
+ \sa registerPropertyPass(), registerElementPass()
+ */
bool PassManager::hasImportedModule(QAnyStringView module) const
{
- return m_visitor->imports().hasType(u"$module$." + module.toString());
+ return PassManagerPrivate::visitor(*this)->imports().hasType(u"$module$." + module.toString());
+}
+
+/*!
+ Returns \c true if warnings of \a category are enabled, \c false otherwise.
+ */
+bool PassManager::isCategoryEnabled(LoggerWarningId category) const
+{
+ return !PassManagerPrivate::visitor(*this)->logger()->isCategoryIgnored(category);
+}
+
+QQmlJSImportVisitor *QQmlSA::PassManagerPrivate::visitor(const QQmlSA::PassManager &manager)
+{
+ return manager.d_func()->m_visitor;
+}
+
+QQmlJSTypeResolver *QQmlSA::PassManagerPrivate::resolver(const QQmlSA::PassManager &manager)
+{
+ return manager.d_func()->m_typeResolver;
}
-QSet<PropertyPass *> PassManager::findPropertyUsePasses(const QQmlSA::Element &element,
- const QString &propertyName)
+QSet<PropertyPass *> PassManagerPrivate::findPropertyUsePasses(const QQmlSA::Element &element,
+ const QString &propertyName)
{
QStringList typeNames { lookupName(element) };
QQmlJSUtils::searchBaseAndExtensionTypes(
- element, [&](const QQmlJSScope::ConstPtr &scope, QQmlJSScope::ExtensionKind mode) {
+ QQmlJSScope::scope(element),
+ [&](const QQmlJSScope::ConstPtr &scope, QQmlJSScope::ExtensionKind mode) {
Q_UNUSED(mode);
- typeNames.append(lookupName(scope));
+ typeNames.append(lookupName(QQmlJSScope::createQQmlSAElement(scope)));
return false;
});
@@ -227,26 +1329,118 @@ QSet<PropertyPass *> PassManager::findPropertyUsePasses(const QQmlSA::Element &e
}
void DebugElementPass::run(const Element &element) {
- emitWarning(u"Type: " + element->baseTypeName(), qmlPlugin);
- if (auto bindings = element->propertyBindings(u"objectName"_s); !bindings.isEmpty()) {
+ emitWarning(u"Type: " + element.baseTypeName(), qmlPlugin);
+ if (auto bindings = element.propertyBindings(u"objectName"_s); !bindings.isEmpty()) {
emitWarning(u"is named: " + bindings.first().stringValue(), qmlPlugin);
}
- if (auto defPropName = element->defaultPropertyName(); !defPropName.isEmpty()) {
- emitWarning(u"binding " + QString::number(element->propertyBindings(defPropName).size())
+ if (auto defPropName = element.defaultPropertyName(); !defPropName.isEmpty()) {
+ emitWarning(u"binding " + QString::number(element.propertyBindings(defPropName).size())
+ u" elements to property "_s + defPropName,
qmlPlugin);
}
}
-bool ElementPass::shouldRun(const Element &)
+/*!
+ \class QQmlSA::LintPlugin
+ \inmodule QtQmlCompiler
+
+ \brief Base class for all static analysis plugins.
+ */
+
+/*!
+ \fn void QQmlSA::LintPlugin::registerPasses(PassManager *manager, const Element &rootElement)
+
+ Adds a pass \a manager that will be executed on \a rootElement.
+ */
+
+/*!
+ \class QQmlSA::ElementPass
+ \inmodule QtQmlCompiler
+
+ \brief Base class for all static analysis passes on elements.
+
+ ElementPass is the simpler of the two analysis passes. It will consider every element in
+ a file. The \l shouldRun() method can be used to filter out irrelevant elements, and the
+ \l run() method is doing the initial work.
+
+ Common tasks suitable for an ElementPass are
+ \list
+ \li checking that properties of an Element are not combined in a nonsensical way
+ \li validating property values (e.g. that a property takes only certain enum values)
+ \li checking behavior dependent on an Element's parent (e.g. not using \l {Item::width}
+ when the parent element is a \c Layout).
+ \endlist
+
+ As shown in the snippet below, it is recommended to do necessary type resolution in the
+ constructor of the ElementPass and cache it in local members, and to implement some
+ filtering via \l shouldRun() to keep the static analysis performant.
+
+ \code
+ using namespace QQmlSA;
+ class MyElementPass : public ElementPass
+ {
+ Element myType;
+ public:
+ MyElementPass(QQmlSA::PassManager *manager)
+ : myType(resolveType("MyModule", "MyType")) {}
+
+ bool shouldRun(const Element &element) override
+ {
+ return element.inherits(myType);
+ }
+ void run(const Element &element) override
+ {
+ // actual pass logic
+ }
+ }
+ \endcode
+
+ ElementPasses have limited insight into how an element's properties are used. If you need
+ that information, consider using a \l PropertyPass instead.
+
+ \note ElementPass will only ever consider instantiable types. Therefore, it is unsuitable
+ to analyze attached types and singletons. Those need to be handled via a PropertyPass.
+ */
+
+/*!
+ \fn void QQmlSA::ElementPass::run(const Element &element)
+
+ Executes if \c shouldRun() returns \c true. Performs the real computation
+ of the pass on \a element.
+ This method is meant to be overridden. Calling the base method is not
+ necessary.
+ */
+
+/*!
+ Controls whether the \c run() function should be executed on the given \a element.
+ Subclasses can override this method to improve performance of the analysis by
+ filtering out elements which are not relevant.
+
+ The default implementation unconditionally returns \c true.
+ */
+bool ElementPass::shouldRun(const Element &element)
{
+ (void)element;
return true;
}
+/*!
+ \class QQmlSA::PropertyPass
+ \inmodule QtQmlCompiler
+
+ \brief Base class for all static analysis passes on properties.
+ */
+
+
PropertyPass::PropertyPass(PassManager *manager) : GenericPass(manager) { }
+/*!
+ Executes whenever a property gets bound to a value.
+ The property \a propertyName of \a element is bound to the \a value within
+ \a bindingScope with \a binding.
+ */
void PropertyPass::onBinding(const Element &element, const QString &propertyName,
- const QQmlJSMetaPropertyBinding &binding, const Element &bindingScope,
+ const QQmlSA::Binding &binding, const Element &bindingScope,
const Element &value)
{
Q_UNUSED(element);
@@ -256,8 +1450,14 @@ void PropertyPass::onBinding(const Element &element, const QString &propertyName
Q_UNUSED(value);
}
+/*!
+ Executes whenever a property is read.
+
+ The property \a propertyName of \a element is read by an instruction within
+ \a readScope defined at \a location.
+ */
void PropertyPass::onRead(const Element &element, const QString &propertyName,
- const Element &readScope, QQmlJS::SourceLocation location)
+ const Element &readScope, QQmlSA::SourceLocation location)
{
Q_UNUSED(element);
Q_UNUSED(propertyName);
@@ -265,14 +1465,21 @@ void PropertyPass::onRead(const Element &element, const QString &propertyName,
Q_UNUSED(location);
}
+/*!
+ Executes whenever a property is written to.
+
+ The property \a propertyName of \a element is written to by an instruction
+ within \a writeScope defined at \a location. The type of the expression
+ written to \a propertyName is \a expressionType.
+ */
void PropertyPass::onWrite(const Element &element, const QString &propertyName,
- const Element &value, const Element &writeScope,
- QQmlJS::SourceLocation location)
+ const Element &expressionType, const Element &writeScope,
+ QQmlSA::SourceLocation location)
{
Q_UNUSED(element);
Q_UNUSED(propertyName);
Q_UNUSED(writeScope);
- Q_UNUSED(value);
+ Q_UNUSED(expressionType);
Q_UNUSED(location);
}
@@ -281,48 +1488,280 @@ DebugPropertyPass::DebugPropertyPass(QQmlSA::PassManager *manager) : QQmlSA::Pro
}
void DebugPropertyPass::onRead(const QQmlSA::Element &element, const QString &propertyName,
- const QQmlSA::Element &readScope, QQmlJS::SourceLocation location)
+ const QQmlSA::Element &readScope, QQmlSA::SourceLocation location)
{
emitWarning(u"onRead "_s
- + (element->internalName().isEmpty() ? element->baseTypeName()
- : element->internalName())
- + u' ' + propertyName + u' ' + readScope->internalName() + u' '
- + QString::number(location.startLine) + u':'
- + QString::number(location.startColumn),
+ + (QQmlJSScope::scope(element)->internalName().isEmpty()
+ ? element.baseTypeName()
+ : QQmlJSScope::scope(element)->internalName())
+ + u' ' + propertyName + u' ' + QQmlJSScope::scope(readScope)->internalName()
+ + u' ' + QString::number(location.startLine()) + u':'
+ + QString::number(location.startColumn()),
qmlPlugin, location);
}
void DebugPropertyPass::onBinding(const QQmlSA::Element &element, const QString &propertyName,
- const QQmlJSMetaPropertyBinding &binding,
+ const QQmlSA::Binding &binding,
const QQmlSA::Element &bindingScope, const QQmlSA::Element &value)
{
- const auto location = binding.sourceLocation();
+ const auto location = QQmlSA::SourceLocation{ binding.sourceLocation() };
emitWarning(u"onBinding element: '"_s
- + (element->internalName().isEmpty() ? element->baseTypeName()
- : element->internalName())
+ + (QQmlJSScope::scope(element)->internalName().isEmpty()
+ ? element.baseTypeName()
+ : QQmlJSScope::scope(element)->internalName())
+ u"' property: '"_s + propertyName + u"' value: '"_s
- + (value.isNull()
- ? u"NULL"_s
- : (value->internalName().isNull() ? value->baseTypeName()
- : value->internalName()))
+ + (value.isNull() ? u"NULL"_s
+ : (QQmlJSScope::scope(value)->internalName().isNull()
+ ? value.baseTypeName()
+ : QQmlJSScope::scope(value)->internalName()))
+ u"' binding_scope: '"_s
- + (bindingScope->internalName().isEmpty() ? bindingScope->baseTypeName()
- : bindingScope->internalName())
- + u"' "_s + QString::number(location.startLine) + u':'
- + QString::number(location.startColumn),
+ + (QQmlJSScope::scope(bindingScope)->internalName().isEmpty()
+ ? bindingScope.baseTypeName()
+ : QQmlJSScope::scope(bindingScope)->internalName())
+ + u"' "_s + QString::number(location.startLine()) + u':'
+ + QString::number(location.startColumn()),
qmlPlugin, location);
}
void DebugPropertyPass::onWrite(const QQmlSA::Element &element, const QString &propertyName,
const QQmlSA::Element &value, const QQmlSA::Element &writeScope,
- QQmlJS::SourceLocation location)
+ QQmlSA::SourceLocation location)
{
- emitWarning(u"onWrite "_s + element->baseTypeName() + u' ' + propertyName + u' '
- + value->internalName() + u' ' + writeScope->internalName() + u' '
- + QString::number(location.startLine) + u':'
- + QString::number(location.startColumn),
+ emitWarning(u"onWrite "_s + element.baseTypeName() + u' ' + propertyName + u' '
+ + QQmlJSScope::scope(value)->internalName() + u' '
+ + QQmlJSScope::scope(writeScope)->internalName() + u' '
+ + QString::number(location.startLine()) + u':'
+ + QString::number(location.startColumn()),
qmlPlugin, location);
}
+
+/*!
+ Returns the list of element passes.
+ */
+std::vector<std::shared_ptr<ElementPass>> PassManager::elementPasses() const
+{
+ Q_D(const PassManager);
+ return d->m_elementPasses;
+}
+
+/*!
+ Returns the list of property passes.
+ */
+std::multimap<QString, PropertyPassInfo> PassManager::propertyPasses() const
+{
+ Q_D(const PassManager);
+ return d->m_propertyPasses;
+}
+
+/*!
+ Returns bindings by their source location.
+ */
+std::unordered_map<quint32, BindingInfo> PassManager::bindingsByLocation() const
+{
+ Q_D(const PassManager);
+ return d->m_bindingsByLocation;
+}
+
+FixSuggestionPrivate::FixSuggestionPrivate(FixSuggestion *interface) : q_ptr{ interface } { }
+
+FixSuggestionPrivate::FixSuggestionPrivate(FixSuggestion *interface, const QString &fixDescription,
+ const QQmlSA::SourceLocation &location,
+ const QString &replacement)
+ : m_fixSuggestion{ fixDescription, QQmlSA::SourceLocationPrivate::sourceLocation(location),
+ replacement },
+ q_ptr{ interface }
+{
+}
+
+FixSuggestionPrivate::FixSuggestionPrivate(FixSuggestion *interface,
+ const FixSuggestionPrivate &other)
+ : m_fixSuggestion{ other.m_fixSuggestion }, q_ptr{ interface }
+{
+}
+
+FixSuggestionPrivate::FixSuggestionPrivate(FixSuggestion *interface, FixSuggestionPrivate &&other)
+ : m_fixSuggestion{ std::move(other.m_fixSuggestion) }, q_ptr{ interface }
+{
+}
+
+QString FixSuggestionPrivate::fixDescription() const
+{
+ return m_fixSuggestion.fixDescription();
+}
+
+QQmlSA::SourceLocation FixSuggestionPrivate::location() const
+{
+ return QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(m_fixSuggestion.location());
+}
+
+QString FixSuggestionPrivate::replacement() const
+{
+ return m_fixSuggestion.replacement();
+}
+
+void FixSuggestionPrivate::setFileName(const QString &fileName)
+{
+ m_fixSuggestion.setFilename(fileName);
+}
+
+QString FixSuggestionPrivate::fileName() const
+{
+ return m_fixSuggestion.filename();
+}
+
+void FixSuggestionPrivate::setHint(const QString &hint)
+{
+ m_fixSuggestion.setHint(hint);
+}
+
+QString FixSuggestionPrivate::hint() const
+{
+ return m_fixSuggestion.hint();
+}
+
+void FixSuggestionPrivate::setAutoApplicable(bool autoApplicable)
+{
+ m_fixSuggestion.setAutoApplicable(autoApplicable);
+}
+
+bool FixSuggestionPrivate::isAutoApplicable() const
+{
+ return m_fixSuggestion.isAutoApplicable();
+}
+
+QQmlJSFixSuggestion &FixSuggestionPrivate::fixSuggestion(FixSuggestion &saFixSuggestion)
+{
+ return saFixSuggestion.d_func()->m_fixSuggestion;
+}
+
+const QQmlJSFixSuggestion &FixSuggestionPrivate::fixSuggestion(const FixSuggestion &saFixSuggestion)
+{
+ return saFixSuggestion.d_func()->m_fixSuggestion;
+}
+
+/*!
+ \class QQmlSA::FixSuggestion
+ \inmodule QtQmlCompiler
+
+ \brief Represents a suggested fix for an issue in the source code.
+ */
+
+
+FixSuggestion::FixSuggestion(const QString &fixDescription, const QQmlSA::SourceLocation &location,
+ const QString &replacement)
+ : d_ptr{ new FixSuggestionPrivate{ this, fixDescription, location, replacement } }
+{
+}
+
+FixSuggestion::FixSuggestion(const FixSuggestion &other)
+ : d_ptr{ new FixSuggestionPrivate{ this, *other.d_func() } }
+{
+}
+
+FixSuggestion::FixSuggestion(FixSuggestion &&other) noexcept
+ : d_ptr{ new FixSuggestionPrivate{ this, std::move(*other.d_func()) } }
+{
}
+FixSuggestion &FixSuggestion::operator=(const FixSuggestion &other)
+{
+ if (*this == other)
+ return *this;
+
+ d_func()->m_fixSuggestion = other.d_func()->m_fixSuggestion;
+ return *this;
+}
+
+FixSuggestion &FixSuggestion::operator=(FixSuggestion &&other) noexcept
+{
+ if (*this == other)
+ return *this;
+
+ d_func()->m_fixSuggestion = std::move(other.d_func()->m_fixSuggestion);
+ return *this;
+}
+
+FixSuggestion::~FixSuggestion() = default;
+
+/*!
+ Returns the description of the fix.
+ */
+QString QQmlSA::FixSuggestion::fixDescription() const
+{
+ return FixSuggestionPrivate::fixSuggestion(*this).fixDescription();
+}
+
+/*!
+ Returns the location where the fix would be applied.
+ */
+QQmlSA::SourceLocation FixSuggestion::location() const
+{
+ return QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
+ FixSuggestionPrivate::fixSuggestion(*this).location());
+}
+
+/*!
+ Returns the fix that will replace the problematic source code.
+ */
+QString FixSuggestion::replacement() const
+{
+ return FixSuggestionPrivate::fixSuggestion(*this).replacement();
+}
+
+/*!
+ Sets \a fileName as the name of the file where this fix suggestion applies.
+ */
+void FixSuggestion::setFileName(const QString &fileName)
+{
+ FixSuggestionPrivate::fixSuggestion(*this).setFilename(fileName);
+}
+
+/*!
+ Returns the name of the file where this fix suggestion applies.
+ */
+QString FixSuggestion::fileName() const
+{
+ return FixSuggestionPrivate::fixSuggestion(*this).filename();
+}
+
+/*!
+ Sets \a hint as the hint for this fix suggestion.
+ */
+void FixSuggestion::setHint(const QString &hint)
+{
+ FixSuggestionPrivate::fixSuggestion(*this).setHint(hint);
+}
+
+/*!
+ Returns the hint for this fix suggestion.
+ */
+QString FixSuggestion::hint() const
+{
+ return FixSuggestionPrivate::fixSuggestion(*this).hint();
+}
+
+/*!
+ Sets \a autoApplicable to determine whether this suggested fix can be
+ applied automatically.
+ */
+void FixSuggestion::setAutoApplicable(bool autoApplicable)
+{
+ return FixSuggestionPrivate::fixSuggestion(*this).setAutoApplicable(autoApplicable);
+}
+
+/*!
+ Returns whether this suggested fix can be applied automatically.
+ */
+bool QQmlSA::FixSuggestion::isAutoApplicable() const
+{
+ return FixSuggestionPrivate::fixSuggestion(*this).isAutoApplicable();
+}
+
+bool FixSuggestion::operatorEqualsImpl(const FixSuggestion &lhs, const FixSuggestion &rhs)
+{
+ return lhs.d_func()->m_fixSuggestion == rhs.d_func()->m_fixSuggestion;
+}
+
+} // namespace QQmlSA
+
QT_END_NAMESPACE
diff --git a/src/qmlcompiler/qqmlsa.h b/src/qmlcompiler/qqmlsa.h
new file mode 100644
index 0000000000..e63f9ea5ab
--- /dev/null
+++ b/src/qmlcompiler/qqmlsa.h
@@ -0,0 +1,435 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef QQMLSA_H
+#define QQMLSA_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is part of the qmllint plugin API, with limited compatibility guarantees.
+// Usage of this API may make your code source and binary incompatible with
+// future versions of Qt.
+//
+
+#include <QtQmlCompiler/qqmlsaconstants.h>
+#include <QtQmlCompiler/qqmljsloggingutils.h>
+
+#include <QtQmlCompiler/qtqmlcompilerexports.h>
+
+#include <QtCore/qhash.h>
+#include <QtCore/qsharedpointer.h>
+#include <QtCore/qplugin.h>
+#include <QtQmlCompiler/qqmlsasourcelocation.h>
+
+#include <unordered_map>
+
+QT_BEGIN_NAMESPACE
+
+namespace QQmlSA {
+
+class BindingPrivate;
+class BindingsPrivate;
+class Element;
+class ElementPass;
+class FixSuggestion;
+class FixSuggestionPrivate;
+class GenericPassPrivate;
+class MethodPrivate;
+class MethodsPrivate;
+class PassManager;
+class PassManagerPrivate;
+class PropertyPass;
+class PropertyPrivate;
+enum class AccessSemantics;
+struct BindingInfo;
+struct PropertyPassInfo;
+
+enum class MethodType { Signal, Slot, Method, StaticMethod };
+
+class Q_QMLCOMPILER_EXPORT Binding
+{
+ Q_DECLARE_PRIVATE(Binding)
+
+public:
+ class Q_QMLCOMPILER_EXPORT Bindings
+ {
+ Q_DECLARE_PRIVATE(Bindings)
+
+ public:
+ Bindings();
+ Bindings(const Bindings &);
+ ~Bindings();
+
+ QMultiHash<QString, Binding>::const_iterator begin() const { return constBegin(); }
+ QMultiHash<QString, Binding>::const_iterator end() const { return constEnd(); }
+ QMultiHash<QString, Binding>::const_iterator constBegin() const;
+ QMultiHash<QString, Binding>::const_iterator constEnd() const;
+
+ private:
+ std::unique_ptr<BindingsPrivate> d_ptr;
+ };
+
+ Binding();
+ Binding(const Binding &);
+ Binding(Binding &&) noexcept;
+ Binding &operator=(const Binding &);
+ Binding &operator=(Binding &&) noexcept;
+ ~Binding();
+
+ Element groupType() const;
+ BindingType bindingType() const;
+ QString stringValue() const;
+ QString propertyName() const;
+ Element attachingType() const;
+ QQmlSA::SourceLocation sourceLocation() const;
+ double numberValue() const;
+ ScriptBindingKind scriptKind() const;
+ bool hasObject() const;
+ Element objectType() const;
+ bool hasUndefinedScriptValue() const;
+
+ friend bool operator==(const Binding &lhs, const Binding &rhs)
+ {
+ return operatorEqualsImpl(lhs, rhs);
+ }
+ friend bool operator!=(const Binding &lhs, const Binding &rhs)
+ {
+ return !operatorEqualsImpl(lhs, rhs);
+ }
+
+ static bool isLiteralBinding(BindingType);
+
+private:
+ static bool operatorEqualsImpl(const Binding &, const Binding &);
+
+ std::unique_ptr<BindingPrivate> d_ptr;
+};
+
+class Q_QMLCOMPILER_EXPORT Method
+{
+ Q_DECLARE_PRIVATE(Method)
+
+public:
+ class Q_QMLCOMPILER_EXPORT Methods
+ {
+ Q_DECLARE_PRIVATE(Methods)
+
+ public:
+ Methods();
+ Methods(const Methods &);
+ ~Methods();
+
+ QMultiHash<QString, Method>::const_iterator begin() const { return constBegin(); }
+ QMultiHash<QString, Method>::const_iterator end() const { return constEnd(); }
+ QMultiHash<QString, Method>::const_iterator constBegin() const;
+ QMultiHash<QString, Method>::const_iterator constEnd() const;
+
+ private:
+ std::unique_ptr<MethodsPrivate> d_ptr;
+ };
+
+ Method();
+ Method(const Method &);
+ Method(Method &&) noexcept;
+ Method &operator=(const Method &);
+ Method &operator=(Method &&) noexcept;
+ ~Method();
+
+ QString methodName() const;
+ QQmlSA::SourceLocation sourceLocation() const;
+ MethodType methodType() const;
+
+ friend bool operator==(const Method &lhs, const Method &rhs)
+ {
+ return operatorEqualsImpl(lhs, rhs);
+ }
+ friend bool operator!=(const Method &lhs, const Method &rhs)
+ {
+ return !operatorEqualsImpl(lhs, rhs);
+ }
+
+private:
+ static bool operatorEqualsImpl(const Method &, const Method &);
+
+ std::unique_ptr<MethodPrivate> d_ptr;
+};
+
+class Q_QMLCOMPILER_EXPORT Property
+{
+ Q_DECLARE_PRIVATE(Property)
+
+public:
+ Property();
+ Property(const Property &);
+ Property(Property &&) noexcept;
+ Property &operator=(const Property &);
+ Property &operator=(Property &&) noexcept;
+ ~Property();
+
+ QString typeName() const;
+ bool isValid() const;
+ bool isReadonly() const;
+ QQmlSA::Element type() const;
+
+ friend bool operator==(const Property &lhs, const Property &rhs)
+ {
+ return operatorEqualsImpl(lhs, rhs);
+ }
+
+ friend bool operator!=(const Property &lhs, const Property &rhs)
+ {
+ return !operatorEqualsImpl(lhs, rhs);
+ }
+
+private:
+ static bool operatorEqualsImpl(const Property &, const Property &);
+
+ std::unique_ptr<PropertyPrivate> d_ptr;
+};
+
+class Q_QMLCOMPILER_EXPORT Element
+{
+ friend class QT_PREPEND_NAMESPACE(QQmlJSScope);
+
+public:
+ Element();
+ Element(const Element &);
+ Element(Element &&other) noexcept
+ {
+ memcpy(m_data, other.m_data, sizeofElement);
+ memset(other.m_data, 0, sizeofElement);
+ }
+ Element &operator=(const Element &);
+ QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_MOVE_AND_SWAP(Element)
+ ~Element();
+
+ ScopeType scopeType() const;
+ Element baseType() const;
+ QString baseTypeName() const;
+ Element parentScope() const;
+ bool inherits(const Element &) const;
+
+ bool isNull() const;
+ QString internalId() const;
+ AccessSemantics accessSemantics() const;
+ bool isComposite() const;
+
+ bool hasProperty(const QString &propertyName) const;
+ bool hasOwnProperty(const QString &propertyName) const;
+ Property property(const QString &propertyName) const;
+ bool isPropertyRequired(const QString &propertyName) const;
+ QString defaultPropertyName() const;
+
+ bool hasMethod(const QString &methodName) const;
+ Method::Methods ownMethods() const;
+
+ QQmlSA::SourceLocation sourceLocation() const;
+ QString filePath() const;
+
+ bool hasPropertyBindings(const QString &name) const;
+ bool hasOwnPropertyBindings(const QString &propertyName) const;
+
+ Binding::Bindings ownPropertyBindings() const;
+ Binding::Bindings ownPropertyBindings(const QString &propertyName) const;
+ QList<Binding> propertyBindings(const QString &propertyName) const;
+
+ explicit operator bool() const;
+ bool operator!() const;
+
+ QString name() const;
+
+ friend inline bool operator==(const QQmlSA::Element &lhs, const QQmlSA::Element &rhs)
+ {
+ return operatorEqualsImpl(lhs, rhs);
+ }
+ friend inline bool operator!=(const Element &lhs, const Element &rhs) { return !(lhs == rhs); }
+
+ friend inline qsizetype qHash(const Element &key, qsizetype seed = 0) noexcept
+ {
+ return qHashImpl(key, seed);
+ }
+
+private:
+ static bool operatorEqualsImpl(const Element &, const Element &);
+ static qsizetype qHashImpl(const Element &key, qsizetype seed) noexcept;
+
+ static constexpr qsizetype sizeofElement = 2 * sizeof(QSharedPointer<int>);
+ alignas(QSharedPointer<int>) char m_data[sizeofElement];
+
+ void swap(Element &other) noexcept
+ {
+ char t[sizeofElement];
+ memcpy(t, m_data, sizeofElement);
+ memcpy(m_data, other.m_data, sizeofElement);
+ memcpy(other.m_data, t, sizeofElement);
+ }
+ friend void swap(Element &lhs, Element &rhs) noexcept { lhs.swap(rhs); }
+};
+
+class Q_QMLCOMPILER_EXPORT GenericPass
+{
+ Q_DECLARE_PRIVATE(GenericPass)
+ Q_DISABLE_COPY_MOVE(GenericPass)
+
+public:
+ GenericPass(PassManager *manager);
+ virtual ~GenericPass();
+
+ void emitWarning(QAnyStringView diagnostic, LoggerWarningId id);
+ void emitWarning(QAnyStringView diagnostic, LoggerWarningId id,
+ QQmlSA::SourceLocation srcLocation);
+ void emitWarning(QAnyStringView diagnostic, LoggerWarningId id,
+ QQmlSA::SourceLocation srcLocation, const QQmlSA::FixSuggestion &fix);
+
+ Element resolveTypeInFileScope(QAnyStringView typeName);
+ Element resolveAttachedInFileScope(QAnyStringView typeName);
+ Element resolveType(QAnyStringView moduleName, QAnyStringView typeName); // #### TODO: revisions
+ Element resolveBuiltinType(QAnyStringView typeName) const;
+ Element resolveAttached(QAnyStringView moduleName, QAnyStringView typeName);
+ Element resolveLiteralType(const Binding &binding);
+
+ Element resolveIdToElement(QAnyStringView id, const Element &context);
+ QString resolveElementToId(const Element &element, const Element &context);
+
+ QString sourceCode(QQmlSA::SourceLocation location);
+
+private:
+ std::unique_ptr<GenericPassPrivate> d_ptr;
+};
+
+class Q_QMLCOMPILER_EXPORT PassManager
+{
+ Q_DISABLE_COPY_MOVE(PassManager)
+ Q_DECLARE_PRIVATE(PassManager)
+
+public:
+ void registerElementPass(std::unique_ptr<ElementPass> pass);
+ bool registerPropertyPass(std::shared_ptr<PropertyPass> pass, QAnyStringView moduleName,
+ QAnyStringView typeName,
+ QAnyStringView propertyName = QAnyStringView(),
+ bool allowInheritance = true);
+ void analyze(const Element &root);
+
+ bool hasImportedModule(QAnyStringView name) const;
+
+ bool isCategoryEnabled(LoggerWarningId category) const;
+
+ std::vector<std::shared_ptr<ElementPass>> elementPasses() const;
+ std::multimap<QString, PropertyPassInfo> propertyPasses() const;
+ std::unordered_map<quint32, BindingInfo> bindingsByLocation() const;
+
+private:
+ PassManager();
+ ~PassManager();
+
+ std::unique_ptr<PassManagerPrivate> d_ptr;
+};
+
+class Q_QMLCOMPILER_EXPORT LintPlugin
+{
+public:
+ LintPlugin() = default;
+ virtual ~LintPlugin() = default;
+
+ Q_DISABLE_COPY_MOVE(LintPlugin)
+
+ virtual void registerPasses(PassManager *manager, const Element &rootElement) = 0;
+};
+
+class Q_QMLCOMPILER_EXPORT PropertyPass : public GenericPass
+{
+public:
+ PropertyPass(PassManager *manager);
+
+ virtual void onBinding(const QQmlSA::Element &element, const QString &propertyName,
+ const QQmlSA::Binding &binding, const QQmlSA::Element &bindingScope,
+ const QQmlSA::Element &value);
+ virtual void onRead(const QQmlSA::Element &element, const QString &propertyName,
+ const QQmlSA::Element &readScope, QQmlSA::SourceLocation location);
+ virtual void onWrite(const QQmlSA::Element &element, const QString &propertyName,
+ const QQmlSA::Element &value, const QQmlSA::Element &writeScope,
+ QQmlSA::SourceLocation location);
+};
+
+class Q_QMLCOMPILER_EXPORT ElementPass : public GenericPass
+{
+public:
+ ElementPass(PassManager *manager) : GenericPass(manager) { }
+
+ virtual bool shouldRun(const Element &element);
+ virtual void run(const Element &element) = 0;
+};
+
+class Q_QMLCOMPILER_EXPORT DebugElementPass : public ElementPass
+{
+ void run(const Element &element) override;
+};
+
+class Q_QMLCOMPILER_EXPORT DebugPropertyPass : public QQmlSA::PropertyPass
+{
+public:
+ DebugPropertyPass(QQmlSA::PassManager *manager);
+
+ void onRead(const QQmlSA::Element &element, const QString &propertyName,
+ const QQmlSA::Element &readScope, QQmlSA::SourceLocation location) override;
+ void onBinding(const QQmlSA::Element &element, const QString &propertyName,
+ const QQmlSA::Binding &binding, const QQmlSA::Element &bindingScope,
+ const QQmlSA::Element &value) override;
+ void onWrite(const QQmlSA::Element &element, const QString &propertyName,
+ const QQmlSA::Element &value, const QQmlSA::Element &writeScope,
+ QQmlSA::SourceLocation location) override;
+};
+
+class Q_QMLCOMPILER_EXPORT FixSuggestion
+{
+ Q_DECLARE_PRIVATE(FixSuggestion)
+
+public:
+ FixSuggestion(const QString &fixDescription, const QQmlSA::SourceLocation &location,
+ const QString &replacement = QString());
+ FixSuggestion(const FixSuggestion &);
+ FixSuggestion(FixSuggestion &&) noexcept;
+ FixSuggestion &operator=(const FixSuggestion &);
+ FixSuggestion &operator=(FixSuggestion &&) noexcept;
+ ~FixSuggestion();
+
+ QString fixDescription() const;
+ QQmlSA::SourceLocation location() const;
+ QString replacement() const;
+
+ void setFileName(const QString &);
+ QString fileName() const;
+
+ void setHint(const QString &);
+ QString hint() const;
+
+ void setAutoApplicable(bool autoApplicable = true);
+ bool isAutoApplicable() const;
+
+ friend bool operator==(const FixSuggestion &lhs, const FixSuggestion &rhs)
+ {
+ return operatorEqualsImpl(lhs, rhs);
+ }
+
+ friend bool operator!=(const FixSuggestion &lhs, const FixSuggestion &rhs)
+ {
+ return !operatorEqualsImpl(lhs, rhs);
+ }
+
+private:
+ static bool operatorEqualsImpl(const FixSuggestion &, const FixSuggestion &);
+
+ std::unique_ptr<FixSuggestionPrivate> d_ptr;
+};
+
+} // namespace QQmlSA
+
+#define QmlLintPluginInterface_iid "org.qt-project.Qt.Qml.SA.LintPlugin/1.0"
+
+Q_DECLARE_INTERFACE(QQmlSA::LintPlugin, QmlLintPluginInterface_iid)
+
+QT_END_NAMESPACE
+
+#endif // QQMLSA_H
diff --git a/src/qmlcompiler/qqmlsa_p.h b/src/qmlcompiler/qqmlsa_p.h
index 67b6995b54..9c9f557e22 100644
--- a/src/qmlcompiler/qqmlsa_p.h
+++ b/src/qmlcompiler/qqmlsa_p.h
@@ -16,9 +16,9 @@
#include <qtqmlcompilerexports.h>
-#include <private/qqmljsscope_p.h>
#include <private/qqmljslogger_p.h>
#include <QtCore/qset.h>
+#include "qqmljsmetatypes_p.h"
#include <map>
#include <unordered_map>
@@ -33,75 +33,173 @@ class QQmlJSImportVisitor;
namespace QQmlSA {
-// ### FIXME: Replace with a proper PIMPL'd type
-using Element = QQmlJSScope::ConstPtr;
-
+class Bindings;
class GenericPassPrivate;
class PassManager;
-class Q_QMLCOMPILER_EXPORT GenericPass
+enum class AccessSemantics { Reference, Value, None, Sequence };
+
+enum class Flag {
+ Creatable = 0x1,
+ Composite = 0x2,
+ Singleton = 0x4,
+ Script = 0x8,
+ CustomParser = 0x10,
+ Array = 0x20,
+ InlineComponent = 0x40,
+ WrappedInImplicitComponent = 0x80,
+ HasBaseTypeError = 0x100,
+ HasExtensionNamespace = 0x200,
+ IsListProperty = 0x400,
+};
+
+struct BindingInfo
+{
+ QString fullPropertyName;
+ QQmlSA::Binding binding;
+ QQmlSA::Element bindingScope;
+ bool isAttached;
+};
+
+struct PropertyPassInfo
+{
+ QStringList properties;
+ std::shared_ptr<QQmlSA::PropertyPass> pass;
+ bool allowInheritance = true;
+};
+
+class BindingsPrivate
+{
+ friend class QT_PREPEND_NAMESPACE(QQmlJSMetaPropertyBinding);
+ Q_DECLARE_PUBLIC(QQmlSA::Binding::Bindings)
+
+public:
+ explicit BindingsPrivate(QQmlSA::Binding::Bindings *);
+ BindingsPrivate(QQmlSA::Binding::Bindings *, const BindingsPrivate &);
+ BindingsPrivate(QQmlSA::Binding::Bindings *, BindingsPrivate &&);
+ ~BindingsPrivate() = default;
+
+ QMultiHash<QString, Binding>::const_iterator constBegin() const;
+ QMultiHash<QString, Binding>::const_iterator constEnd() const;
+
+ static QQmlSA::Binding::Bindings
+ createBindings(const QMultiHash<QString, QQmlJSMetaPropertyBinding> &);
+ static QQmlSA::Binding::Bindings
+ createBindings(QPair<QMultiHash<QString, QQmlJSMetaPropertyBinding>::const_iterator,
+ QMultiHash<QString, QQmlJSMetaPropertyBinding>::const_iterator>);
+
+private:
+ QMultiHash<QString, Binding> m_bindings;
+ QQmlSA::Binding::Bindings *q_ptr;
+};
+
+class BindingPrivate
{
+ friend class QT_PREPEND_NAMESPACE(QQmlJSMetaPropertyBinding);
+ Q_DECLARE_PUBLIC(Binding)
+
public:
- Q_DISABLE_COPY_MOVE(GenericPass)
- GenericPass(PassManager *manager);
- virtual ~GenericPass();
+ explicit BindingPrivate(Binding *);
+ BindingPrivate(Binding *, const BindingPrivate &);
- void emitWarning(QAnyStringView message, LoggerWarningId id,
- QQmlJS::SourceLocation srcLocation = QQmlJS::SourceLocation());
- Element resolveType(QAnyStringView moduleName, QAnyStringView typeName); // #### TODO: revisions
- Element resolveLiteralType(const QQmlJSMetaPropertyBinding &binding);
+ static QQmlSA::Binding createBinding(const QQmlJSMetaPropertyBinding &);
+ static QQmlJSMetaPropertyBinding binding(QQmlSA::Binding &binding);
+ static const QQmlJSMetaPropertyBinding binding(const QQmlSA::Binding &binding);
private:
- std::unique_ptr<GenericPassPrivate> d; // PIMPL might be overkill
+ QQmlJSMetaPropertyBinding m_binding;
+ Binding *q_ptr;
};
-class Q_QMLCOMPILER_EXPORT ElementPass : public GenericPass
+class MethodPrivate
{
+ friend class QT_PREPEND_NAMESPACE(QQmlJSMetaMethod);
+ Q_DECLARE_PUBLIC(Method)
+
public:
- ElementPass(PassManager *manager) : GenericPass(manager) { }
+ explicit MethodPrivate(Method *);
+ MethodPrivate(Method *, const MethodPrivate &);
- virtual bool shouldRun(const Element &element);
- virtual void run(const Element &element) = 0;
+ QString methodName() const;
+ QQmlSA::SourceLocation sourceLocation() const;
+ MethodType methodType() const;
+
+ static QQmlSA::Method createMethod(const QQmlJSMetaMethod &);
+ static QQmlJSMetaMethod method(const QQmlSA::Method &);
+
+private:
+ QQmlJSMetaMethod m_method;
+ Method *q_ptr;
};
-class Q_QMLCOMPILER_EXPORT PropertyPass : public GenericPass
+class MethodsPrivate
{
+ friend class QT_PREPEND_NAMESPACE(QQmlJSMetaMethod);
+ Q_DECLARE_PUBLIC(QQmlSA::Method::Methods)
+
public:
- PropertyPass(PassManager *manager);
-
- virtual void onBinding(const QQmlSA::Element &element, const QString &propertyName,
- const QQmlJSMetaPropertyBinding &binding,
- const QQmlSA::Element &bindingScope, const QQmlSA::Element &value);
- virtual void onRead(const QQmlSA::Element &element, const QString &propertyName,
- const QQmlSA::Element &readScope, QQmlJS::SourceLocation location);
- virtual void onWrite(const QQmlSA::Element &element, const QString &propertyName,
- const QQmlSA::Element &value, const QQmlSA::Element &writeScope,
- QQmlJS::SourceLocation location);
+ explicit MethodsPrivate(QQmlSA::Method::Methods *);
+ MethodsPrivate(QQmlSA::Method::Methods *, const MethodsPrivate &);
+ MethodsPrivate(QQmlSA::Method::Methods *, MethodsPrivate &&);
+ ~MethodsPrivate() = default;
+
+ QMultiHash<QString, Method>::const_iterator constBegin() const;
+ QMultiHash<QString, Method>::const_iterator constEnd() const;
+
+ static QQmlSA::Method::Methods createMethods(const QMultiHash<QString, QQmlJSMetaMethod> &);
+
+private:
+ QMultiHash<QString, Method> m_methods;
+ QQmlSA::Method::Methods *q_ptr;
};
-class Q_QMLCOMPILER_EXPORT LintPlugin
+class PropertyPrivate
{
+ friend class QT_PREPEND_NAMESPACE(QQmlJSMetaProperty);
+ Q_DECLARE_PUBLIC(QQmlSA::Property)
+
public:
- LintPlugin() = default;
- virtual ~LintPlugin() = default;
+ explicit PropertyPrivate(Property *);
+ PropertyPrivate(Property *, const PropertyPrivate &);
+ PropertyPrivate(Property *, PropertyPrivate &&);
+ ~PropertyPrivate() = default;
+
+ QString typeName() const;
+ bool isValid() const;
+ bool isReadonly() const;
+ QQmlSA::Element type() const;
- Q_DISABLE_COPY_MOVE(LintPlugin)
+ static QQmlJSMetaProperty property(const QQmlSA::Property &property);
+ static QQmlSA::Property createProperty(const QQmlJSMetaProperty &);
- virtual void registerPasses(PassManager *manager, const Element &rootElement) = 0;
+private:
+ QQmlJSMetaProperty m_property;
+ QQmlSA::Property *q_ptr;
};
-// ### FIXME: Make this (at least partially) private again as soon as possible
-class Q_QMLCOMPILER_EXPORT PassManager
+class Q_QMLCOMPILER_EXPORT PassManagerPrivate
{
+ friend class QT_PREPEND_NAMESPACE(QQmlJSScope);
+
public:
- Q_DISABLE_COPY_MOVE(PassManager)
+ Q_DISABLE_COPY_MOVE(PassManagerPrivate)
friend class GenericPass;
- PassManager(QQmlJSImportVisitor *visitor, QQmlJSTypeResolver *resolver)
+ PassManagerPrivate(QQmlJSImportVisitor *visitor, QQmlJSTypeResolver *resolver)
: m_visitor(visitor), m_typeResolver(resolver)
{
- Q_UNUSED(m_typeResolver);
}
+
+ static PassManagerPrivate *get(PassManager *manager) { return manager->d_func(); }
+ static const PassManagerPrivate *get(const PassManager *manager) { return manager->d_func(); }
+ static PassManager *createPassManager(QQmlJSImportVisitor *visitor, QQmlJSTypeResolver *resolver)
+ {
+ PassManager *result = new PassManager();
+ result->d_ptr = std::make_unique<PassManagerPrivate>(visitor, resolver);
+ return result;
+ }
+ static void deletePassManager(PassManager *q) { delete q; }
+
void registerElementPass(std::unique_ptr<ElementPass> pass);
bool registerPropertyPass(std::shared_ptr<PropertyPass> pass, QAnyStringView moduleName,
QAnyStringView typeName,
@@ -111,70 +209,67 @@ public:
bool hasImportedModule(QAnyStringView name) const;
-private:
- friend struct ::QQmlJSTypePropagator;
+ static QQmlJSImportVisitor *visitor(const QQmlSA::PassManager &);
+ static QQmlJSTypeResolver *resolver(const QQmlSA::PassManager &);
+
QSet<PropertyPass *> findPropertyUsePasses(const QQmlSA::Element &element,
const QString &propertyName);
void analyzeWrite(const QQmlSA::Element &element, QString propertyName,
const QQmlSA::Element &value, const QQmlSA::Element &writeScope,
- QQmlJS::SourceLocation location);
+ QQmlSA::SourceLocation location);
void analyzeRead(const QQmlSA::Element &element, QString propertyName,
- const QQmlSA::Element &readScope, QQmlJS::SourceLocation location);
+ const QQmlSA::Element &readScope, QQmlSA::SourceLocation location);
void analyzeBinding(const QQmlSA::Element &element, const QQmlSA::Element &value,
- QQmlJS::SourceLocation location);
-
- struct BindingInfo
- {
- QString fullPropertyName;
- QQmlJSMetaPropertyBinding binding;
- QQmlSA::Element bindingScope;
- bool isAttached;
- };
-
- struct PropertyPassInfo
- {
- QStringList properties;
- std::shared_ptr<PropertyPass> pass;
- bool allowInheritance = true;
- };
+ QQmlSA::SourceLocation location);
void addBindingSourceLocations(const QQmlSA::Element &element,
const QQmlSA::Element &scope = QQmlSA::Element(),
const QString prefix = QString(), bool isAttached = false);
- std::vector<std::unique_ptr<ElementPass>> m_elementPasses;
+ std::vector<std::shared_ptr<ElementPass>> m_elementPasses;
std::multimap<QString, PropertyPassInfo> m_propertyPasses;
std::unordered_map<quint32, BindingInfo> m_bindingsByLocation;
QQmlJSImportVisitor *m_visitor;
QQmlJSTypeResolver *m_typeResolver;
};
-class Q_QMLCOMPILER_EXPORT DebugElementPass : public ElementPass
+class FixSuggestionPrivate
{
- void run(const Element &element) override;
-};
+ Q_DECLARE_PUBLIC(FixSuggestion)
+ friend class QT_PREPEND_NAMESPACE(QQmlJSFixSuggestion);
-class Q_QMLCOMPILER_EXPORT DebugPropertyPass : public QQmlSA::PropertyPass
-{
public:
- DebugPropertyPass(QQmlSA::PassManager *manager);
-
- void onRead(const QQmlSA::Element &element, const QString &propertyName,
- const QQmlSA::Element &readScope, QQmlJS::SourceLocation location) override;
- void onBinding(const QQmlSA::Element &element, const QString &propertyName,
- const QQmlJSMetaPropertyBinding &binding, const QQmlSA::Element &bindingScope,
- const QQmlSA::Element &value) override;
- void onWrite(const QQmlSA::Element &element, const QString &propertyName,
- const QQmlSA::Element &value, const QQmlSA::Element &writeScope,
- QQmlJS::SourceLocation location) override;
-};
-}
+ explicit FixSuggestionPrivate(FixSuggestion *);
+ FixSuggestionPrivate(FixSuggestion *, const QString &fixDescription,
+ const QQmlSA::SourceLocation &location, const QString &replacement);
+ FixSuggestionPrivate(FixSuggestion *, const FixSuggestionPrivate &);
+ FixSuggestionPrivate(FixSuggestion *, FixSuggestionPrivate &&);
+ ~FixSuggestionPrivate() = default;
+
+ QString fixDescription() const;
+ QQmlSA::SourceLocation location() const;
+ QString replacement() const;
-#define QmlLintPluginInterface_iid "org.qt-project.Qt.Qml.SA.LintPlugin/1.0"
+ void setFileName(const QString &);
+ QString fileName() const;
+
+ void setHint(const QString &);
+ QString hint() const;
+
+ void setAutoApplicable(bool autoApplicable = true);
+ bool isAutoApplicable() const;
+
+ static QQmlJSFixSuggestion &fixSuggestion(QQmlSA::FixSuggestion &);
+ static const QQmlJSFixSuggestion &fixSuggestion(const QQmlSA::FixSuggestion &);
+
+private:
+ QQmlJSFixSuggestion m_fixSuggestion;
+ QQmlSA::FixSuggestion *q_ptr;
+};
-Q_DECLARE_INTERFACE(QQmlSA::LintPlugin, QmlLintPluginInterface_iid)
+} // namespace QQmlSA
QT_END_NAMESPACE
diff --git a/src/qmlcompiler/qqmlsaconstants.h b/src/qmlcompiler/qqmlsaconstants.h
new file mode 100644
index 0000000000..019040d259
--- /dev/null
+++ b/src/qmlcompiler/qqmlsaconstants.h
@@ -0,0 +1,50 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef QQMLSACONSTANTS_H
+#define QQMLSACONSTANTS_H
+
+#include <QtCore/qtconfigmacros.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace QQmlSA {
+
+enum class BindingType : unsigned int {
+ Invalid,
+ BoolLiteral,
+ NumberLiteral,
+ StringLiteral,
+ RegExpLiteral,
+ Null,
+ Translation,
+ TranslationById,
+ Script,
+ Object,
+ Interceptor,
+ ValueSource,
+ AttachedProperty,
+ GroupProperty,
+};
+
+enum class ScriptBindingKind : unsigned int {
+ Invalid,
+ PropertyBinding, // property int p: 1 + 1
+ SignalHandler, // onSignal: { ... }
+ ChangeHandler, // onXChanged: { ... }
+};
+
+enum class ScopeType {
+ JSFunctionScope,
+ JSLexicalScope,
+ QMLScope,
+ GroupedPropertyScope,
+ AttachedPropertyScope,
+ EnumScope
+};
+
+} // namespace QQmlSA
+
+QT_END_NAMESPACE
+
+#endif // QQMLSACONSTANTS_H
diff --git a/src/qmlcompiler/qqmlsasourcelocation.cpp b/src/qmlcompiler/qqmlsasourcelocation.cpp
new file mode 100644
index 0000000000..63b61cce5b
--- /dev/null
+++ b/src/qmlcompiler/qqmlsasourcelocation.cpp
@@ -0,0 +1,125 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "qqmlsasourcelocation.h"
+#include "qqmlsasourcelocation_p.h"
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+namespace QQmlSA {
+
+static_assert(SourceLocationPrivate::sizeOfSourceLocation() == sizeof(SourceLocation));
+
+/*!
+ \class QQmlSA::SourceLocation
+ \inmodule QtQmlCompiler
+
+ \brief Represents a location or region in the source code.
+ */
+QQmlSA::SourceLocation::SourceLocation(quint32 offset, quint32 length, quint32 line, quint32 column)
+{
+ new (m_data) QQmlJS::SourceLocation{ offset, length, line, column };
+}
+
+// explicitly defaulted out-of-line for PIMPL
+QQmlSA::SourceLocation::SourceLocation(const SourceLocation &other) = default;
+QQmlSA::SourceLocation & QQmlSA::SourceLocation::operator=(const QQmlSA::SourceLocation &other) = default;
+SourceLocation::~SourceLocation() = default;
+
+bool QQmlSA::SourceLocation::isValid() const
+{
+ return QQmlSA::SourceLocationPrivate::sourceLocation(*this).isValid();
+}
+
+/*!
+ Returns the offset of the beginning of this source location.
+ */
+quint32 QQmlSA::SourceLocation::begin() const
+{
+ return QQmlSA::SourceLocationPrivate::sourceLocation(*this).begin();
+}
+
+/*!
+ Returns the offset of the end of this source location.
+ */
+quint32 QQmlSA::SourceLocation::end() const
+{
+ return QQmlSA::SourceLocationPrivate::sourceLocation(*this).end();
+}
+
+/*!
+ Returns the offset of the beginning of this source location.
+ */
+quint32 QQmlSA::SourceLocation::offset() const
+{
+ return QQmlSA::SourceLocationPrivate::sourceLocation(*this).offset;
+}
+
+/*!
+ Returns the length of this source location.
+ */
+quint32 QQmlSA::SourceLocation::length() const
+{
+ return QQmlSA::SourceLocationPrivate::sourceLocation(*this).length;
+}
+
+/*!
+ Returns the line number containing the beginning of this source location.
+ */
+quint32 QQmlSA::SourceLocation::startLine() const
+{
+ return QQmlSA::SourceLocationPrivate::sourceLocation(*this).startLine;
+}
+
+/*!
+ Returns the column number containing the beginning of this source location.
+ */
+quint32 QQmlSA::SourceLocation::startColumn() const
+{
+ return QQmlSA::SourceLocationPrivate::sourceLocation(*this).startColumn;
+}
+
+/*!
+ Returns a source location of lenth zero pointing to the beginning of this
+ source location.
+ */
+QQmlSA::SourceLocation QQmlSA::SourceLocation::startZeroLengthLocation() const
+{
+ QQmlSA::SourceLocation saLocation;
+ auto &wrappedLocation = reinterpret_cast<QQmlJS::SourceLocation &>(saLocation.m_data);
+ wrappedLocation =
+ QQmlSA::SourceLocationPrivate::sourceLocation(*this).startZeroLengthLocation();
+
+ return saLocation;
+}
+
+/*!
+ Returns a source location of lenth zero pointing to the end of this source
+ location pointing to \a text.
+ */
+QQmlSA::SourceLocation QQmlSA::SourceLocation::endZeroLengthLocation(QStringView text) const
+{
+ QQmlSA::SourceLocation saLocation;
+ auto &wrappedLocation = reinterpret_cast<QQmlJS::SourceLocation &>(saLocation.m_data);
+ wrappedLocation = wrappedLocation.endZeroLengthLocation(text);
+
+ return saLocation;
+}
+
+qsizetype QQmlSA::SourceLocation::qHashImpl(const SourceLocation &location, qsizetype seed)
+{
+ return qHash(QQmlSA::SourceLocationPrivate::sourceLocation(location), seed);
+}
+
+bool QQmlSA::SourceLocation::operatorEqualsImpl(const SourceLocation &lhs,
+ const SourceLocation &rhs)
+{
+ return QQmlSA::SourceLocationPrivate::sourceLocation(lhs)
+ == QQmlSA::SourceLocationPrivate::sourceLocation(rhs);
+}
+
+} // namespace QQmlSA
+
+QT_END_NAMESPACE
diff --git a/src/qmlcompiler/qqmlsasourcelocation.h b/src/qmlcompiler/qqmlsasourcelocation.h
new file mode 100644
index 0000000000..7009d4faef
--- /dev/null
+++ b/src/qmlcompiler/qqmlsasourcelocation.h
@@ -0,0 +1,83 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef QQMLSASOURCELOCATION_H
+#define QQMLSASOURCELOCATION_H
+
+#include <QtQmlCompiler/qtqmlcompilerexports.h>
+
+#include <QtCore/qstringview.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace QQmlJS {
+class SourceLocation;
+} // namespace QQmlJS
+
+namespace QQmlSA {
+
+class SourceLocationPrivate;
+
+class Q_QMLCOMPILER_EXPORT SourceLocation
+{
+ friend class QT_PREPEND_NAMESPACE(QQmlSA::SourceLocationPrivate);
+
+public:
+ explicit SourceLocation(quint32 offset = 0, quint32 length = 0, quint32 line = 0,
+ quint32 column = 0);
+ SourceLocation(const SourceLocation &);
+ SourceLocation(SourceLocation &&other) noexcept
+ {
+ memcpy(m_data, other.m_data, sizeofSourceLocation);
+ memset(other.m_data, 0, sizeofSourceLocation);
+ }
+ SourceLocation &operator=(const SourceLocation &);
+ SourceLocation &operator=(SourceLocation &&other) noexcept
+ {
+ memcpy(m_data, other.m_data, sizeofSourceLocation);
+ memset(other.m_data, 0, sizeofSourceLocation);
+ return *this;
+ }
+ ~SourceLocation();
+
+ bool isValid() const;
+
+ quint32 begin() const;
+ quint32 end() const;
+
+ quint32 offset() const;
+ quint32 length() const;
+ quint32 startLine() const;
+ quint32 startColumn() const;
+
+ SourceLocation startZeroLengthLocation() const;
+ SourceLocation endZeroLengthLocation(QStringView text) const;
+
+ friend qsizetype qHash(const SourceLocation &location, qsizetype seed = 0)
+ {
+ return qHashImpl(location, seed);
+ }
+
+ friend bool operator==(const SourceLocation &lhs, const SourceLocation &rhs)
+ {
+ return operatorEqualsImpl(lhs, rhs);
+ }
+
+ friend bool operator!=(const SourceLocation &lhs, const SourceLocation &rhs)
+ {
+ return !(lhs == rhs);
+ }
+
+private:
+ static qsizetype qHashImpl(const SourceLocation &location, qsizetype seed);
+ static bool operatorEqualsImpl(const SourceLocation &, const SourceLocation &);
+
+ static constexpr qsizetype sizeofSourceLocation = 4 * sizeof(quint32);
+ alignas(int) char m_data[sizeofSourceLocation] = {};
+};
+
+} // namespace QQmlSA
+
+QT_END_NAMESPACE
+
+#endif // QQMLSASOURCELOCATION_H
diff --git a/src/qmlcompiler/qqmlsasourcelocation_p.h b/src/qmlcompiler/qqmlsasourcelocation_p.h
new file mode 100644
index 0000000000..fa78512c46
--- /dev/null
+++ b/src/qmlcompiler/qqmlsasourcelocation_p.h
@@ -0,0 +1,53 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef QQMLSASOURCELOCATION_P_H
+#define QQMLSASOURCELOCATION_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#include "qqmlsasourcelocation.h"
+
+#include <QtQml/private/qqmljssourcelocation_p.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace QQmlSA {
+
+class SourceLocationPrivate
+{
+public:
+ static const QQmlJS::SourceLocation &
+ sourceLocation(const QQmlSA::SourceLocation &sourceLocation)
+ {
+ return reinterpret_cast<const QQmlJS::SourceLocation &>(sourceLocation.m_data);
+ }
+
+ static QQmlSA::SourceLocation
+ createQQmlSASourceLocation(const QQmlJS::SourceLocation &jsLocation)
+ {
+ QQmlSA::SourceLocation saLocation;
+ auto &internal = reinterpret_cast<QQmlJS::SourceLocation &>(saLocation.m_data);
+ internal = jsLocation;
+ return saLocation;
+ }
+
+ static constexpr qsizetype sizeOfSourceLocation()
+ {
+ return SourceLocation::sizeofSourceLocation;
+ }
+};
+
+} // namespace QQmlSA
+
+QT_END_NAMESPACE
+
+#endif // QQMLSASOURCELOCATION_P_H
diff --git a/src/qmlcompiler/qresourcerelocater.cpp b/src/qmlcompiler/qresourcerelocater.cpp
index 05ad059586..3b2eb67156 100644
--- a/src/qmlcompiler/qresourcerelocater.cpp
+++ b/src/qmlcompiler/qresourcerelocater.cpp
@@ -10,6 +10,7 @@
QT_BEGIN_NAMESPACE
/*!
+ \internal
Changes all the paths in resource file \a input so that they are relative to
location \a output and writes the result to resource file \a output.
*/
diff --git a/src/qmlcompiler/qresourcerelocater_p.h b/src/qmlcompiler/qresourcerelocater_p.h
index b0c2471147..4b06a30041 100644
--- a/src/qmlcompiler/qresourcerelocater_p.h
+++ b/src/qmlcompiler/qresourcerelocater_p.h
@@ -14,14 +14,14 @@
//
// We mean it.
-#include <private/qtqmlcompilerexports_p.h>
+#include <qtqmlcompilerexports.h>
#include <QtCore/qstring.h>
#include <QtCore/private/qglobal_p.h>
QT_BEGIN_NAMESPACE
-int Q_QMLCOMPILER_PRIVATE_EXPORT qRelocateResourceFile(const QString &input, const QString &output);
+int Q_QMLCOMPILER_EXPORT qRelocateResourceFile(const QString &input, const QString &output);
QT_END_NAMESPACE