diff options
Diffstat (limited to 'src/corelib')
110 files changed, 10149 insertions, 879 deletions
diff --git a/src/corelib/CMakeLists.txt b/src/corelib/CMakeLists.txt index 32b70a1f288..ea8cf7b9c8e 100644 --- a/src/corelib/CMakeLists.txt +++ b/src/corelib/CMakeLists.txt @@ -175,12 +175,12 @@ qt_internal_add_module(Core kernel/qfunctions_p.h kernel/qiterable.cpp kernel/qiterable.h kernel/qiterable_impl.h kernel/qmath.cpp kernel/qmath.h - kernel/qmetaassociation.cpp + kernel/qmetaassociation.cpp kernel/qmetaassociation.h kernel/qmetacontainer.cpp kernel/qmetacontainer.h kernel/qmetaobject.cpp kernel/qmetaobject.h kernel/qmetaobject_p.h kernel/qmetaobject_moc_p.h kernel/qmetaobjectbuilder.cpp kernel/qmetaobjectbuilder_p.h - kernel/qmetasequence.cpp + kernel/qmetasequence.cpp kernel/qmetasequence.h kernel/qmetatype.cpp kernel/qmetatype.h kernel/qmetatype_p.h kernel/qmimedata.cpp kernel/qmimedata.h kernel/qtmetamacros.h kernel/qtmocconstants.h kernel/qtmochelpers.h @@ -583,6 +583,13 @@ if(QT_FEATURE_async_io) SOURCES io/qrandomaccessasyncfile_darwin.mm ) + elseif((LINUX AND QT_FEATURE_liburing) OR (WIN32 AND QT_FEATURE_windows_ioring)) + qt_internal_extend_target(Core + SOURCES + io/qrandomaccessasyncfile_qioring.cpp + DEFINES + QT_RANDOMACCESSASYNCFILE_QIORING + ) elseif(QT_FEATURE_thread AND QT_FEATURE_future) # TODO: This should become the last (fallback) condition later. # We migth also want to rewrite it so that it does not depend on @@ -749,6 +756,18 @@ qt_internal_extend_target(Core CONDITION INTEGRITY --pending_instantiations=128 ) +qt_internal_extend_target(Core CONDITION QT_FEATURE_liburing + SOURCES + io/qioring.cpp io/qioring_linux.cpp io/qioring_p.h + LIBRARIES + uring +) + +qt_internal_extend_target(Core CONDITION QT_FEATURE_windows_ioring + SOURCES + io/qioring.cpp io/qioring_win.cpp io/qioring_p.h +) + # Workaround for QTBUG-101411 # Remove if QCC (gcc version 8.3.0) for QNX 7.1.0 is no longer supported qt_internal_extend_target(Core CONDITION QCC AND (CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL "8.3.0") @@ -1217,6 +1236,7 @@ qt_internal_extend_target(Core CONDITION QT_FEATURE_itemmodel itemmodels/qabstractitemmodel.cpp itemmodels/qabstractitemmodel.h itemmodels/qabstractitemmodel_p.h itemmodels/qitemselectionmodel.cpp itemmodels/qitemselectionmodel.h itemmodels/qitemselectionmodel_p.h itemmodels/qrangemodel.h itemmodels/qrangemodel_impl.h itemmodels/qrangemodel.cpp + itemmodels/qrangemodeladapter.h itemmodels/qrangemodeladapter_impl.h ) qt_internal_extend_target(Core CONDITION QT_FEATURE_proxymodel diff --git a/src/corelib/Qt6AndroidMacros.cmake b/src/corelib/Qt6AndroidMacros.cmake index 6a83e947146..be362ba1925 100644 --- a/src/corelib/Qt6AndroidMacros.cmake +++ b/src/corelib/Qt6AndroidMacros.cmake @@ -106,6 +106,27 @@ function(qt6_add_android_dynamic_features target) endif() endfunction() + +function(qt_add_android_dynamic_feature_java_source_dirs) + qt6_add_android_dynamic_feature_java_source_dirs(${ARGV}) +endfunction() + +# Add java source directories for dynamic feature. Intermediate solution until java library +# support exists. +function(qt6_add_android_dynamic_feature_java_source_dirs target) + + set(opt_args "") + set(single_args "") + set(multi_args + SOURCE_DIRS + ) + cmake_parse_arguments(PARSE_ARGV 1 arg "${opt_args}" "${single_args}" "${multi_args}") + if(arg_SOURCE_DIRS) + set_property(TARGET ${target} APPEND PROPERTY + _qt_android_gradle_java_source_dirs ${arg_SOURCE_DIRS}) + endif() +endfunction() + # Generate the deployment settings json file for a cmake target. function(qt6_android_generate_deployment_settings target) # Information extracted from mkspecs/features/android/android_deployment_settings.prf diff --git a/src/corelib/Qt6CoreMacros.cmake b/src/corelib/Qt6CoreMacros.cmake index 6f3a9fd05f5..c1f9aee0180 100644 --- a/src/corelib/Qt6CoreMacros.cmake +++ b/src/corelib/Qt6CoreMacros.cmake @@ -3264,7 +3264,7 @@ function(_qt_internal_setup_deploy_support) list(JOIN candidate_paths "\n " candidate_paths_joined) - if(NOT QT_NO_QTPATHS_DEPLOYMENT_WARNING AND NOT target_qtpaths_path) + if(WIN32 AND NOT QT_NO_QTPATHS_DEPLOYMENT_WARNING AND NOT target_qtpaths_path) message(WARNING "No qtpaths executable found for deployment purposes. Candidates searched: \n " "${candidate_paths_joined}" @@ -3677,21 +3677,6 @@ macro(qt6_standard_project_setup) if(NOT DEFINED QT_I18N_SOURCE_LANGUAGE) set(QT_I18N_SOURCE_LANGUAGE ${__qt_sps_arg_I18N_SOURCE_LANGUAGE}) endif() - - if(CMAKE_GENERATOR STREQUAL "Xcode") - # Ensure we always use device SDK for Xcode for single-arch Qt builds - set(qt_osx_arch_count 0) - if(QT_OSX_ARCHITECTURES) - list(LENGTH QT_OSX_ARCHITECTURES qt_osx_arch_count) - endif() - if(NOT qt_osx_arch_count GREATER 1 AND "${CMAKE_OSX_SYSROOT}" MATCHES "^[a-z]+simulator$") - # Xcode expects the base SDK to be the device SDK - set(simulator_sysroot "${CMAKE_OSX_SYSROOT}") - string(REGEX REPLACE "simulator" "os" CMAKE_OSX_SYSROOT "${CMAKE_OSX_SYSROOT}") - set(CMAKE_OSX_SYSROOT "${CMAKE_OSX_SYSROOT}" CACHE STRING "" FORCE) - set(CMAKE_XCODE_ATTRIBUTE_SUPPORTED_PLATFORMS "${simulator_sysroot}") - endif() - endif() endif() endmacro() diff --git a/src/corelib/animation/qabstractanimation.cpp b/src/corelib/animation/qabstractanimation.cpp index c3e1ba4010f..8a343b15eeb 100644 --- a/src/corelib/animation/qabstractanimation.cpp +++ b/src/corelib/animation/qabstractanimation.cpp @@ -858,6 +858,7 @@ qint64 QAnimationDriver::elapsed() const */ /*! + \internal The default animation driver just spins the timer... */ QDefaultAnimationDriver::QDefaultAnimationDriver(QUnifiedTimer *timer) diff --git a/src/corelib/compat/removed_api.cpp b/src/corelib/compat/removed_api.cpp index 7fe8aeb63b7..63fce94dfac 100644 --- a/src/corelib/compat/removed_api.cpp +++ b/src/corelib/compat/removed_api.cpp @@ -1291,13 +1291,6 @@ QByteArray QMetaEnum::valueToKeys(int value) const #include "qmutex.h" -#include "qbytearray.h" - -QByteArray QByteArray::percentDecoded(char percent) const -{ - return fromPercentEncoding(*this, percent); -} - #if QT_CONFIG(thread) void QBasicMutex::destroyInternal(QMutexPrivate *d) { @@ -1495,6 +1488,13 @@ bool QObject::doSetProperty(const char *name, const QVariant *lvalue, QVariant * #if QT_CORE_REMOVED_SINCE(6, 11) +#include "qbytearray.h" + +QByteArray QByteArray::percentDecoded(char percent) const +{ + return fromPercentEncoding(*this, percent); +} + #if QT_CONFIG(thread) // some of the previously inlined API became removed #include "qreadwritelock.h" diff --git a/src/corelib/configure.cmake b/src/corelib/configure.cmake index edcfba0f6ce..90a0e359c9f 100644 --- a/src/corelib/configure.cmake +++ b/src/corelib/configure.cmake @@ -31,7 +31,10 @@ qt_find_package_extend_sbom(TARGETS GLIB2::GLIB2 LICENSE_EXPRESSION "LGPL-2.1-or-later" ) qt_find_package(ICU 50.1 COMPONENTS i18n uc data PROVIDED_TARGETS ICU::i18n ICU::uc ICU::data - MODULE_NAME core QMAKE_LIB icu) + MODULE_NAME core QMAKE_LIB icu + VCPKG_PORT icu + VCPKG_PLATFORM !windows +) if(QT_FEATURE_dlopen) qt_add_qmake_lib_dependency(icu libdl) @@ -40,6 +43,8 @@ qt_find_package(JeMalloc MODULE PROVIDED_TARGETS PkgConfig::JeMalloc MODULE_NAME core QMAKE_LIB jemalloc) qt_find_package(Libsystemd MODULE PROVIDED_TARGETS PkgConfig::Libsystemd MODULE_NAME core QMAKE_LIB journald) +qt_find_package(Liburing MODULE + PROVIDED_TARGETS PkgConfig::Liburing MODULE_NAME global QMAKE_LIB liburing) qt_find_package(WrapAtomic MODULE PROVIDED_TARGETS WrapAtomic::WrapAtomic MODULE_NAME core QMAKE_LIB libatomic) qt_find_package(Libb2 MODULE PROVIDED_TARGETS Libb2::Libb2 MODULE_NAME core QMAKE_LIB libb2) @@ -49,7 +54,9 @@ qt_find_package_extend_sbom(TARGETS Libb2::Libb2 qt_find_package(WrapRt MODULE PROVIDED_TARGETS WrapRt::WrapRt MODULE_NAME core QMAKE_LIB librt) qt_find_package(WrapSystemPCRE2 10.20 MODULE - PROVIDED_TARGETS WrapSystemPCRE2::WrapSystemPCRE2 MODULE_NAME core QMAKE_LIB pcre2) + PROVIDED_TARGETS WrapSystemPCRE2::WrapSystemPCRE2 MODULE_NAME core QMAKE_LIB pcre2 + VCPKG_PORT pcre2 +) set_package_properties(WrapPCRE2 PROPERTIES TYPE REQUIRED) if((QNX) OR QT_FIND_ALL_PACKAGES_ALWAYS) qt_find_package(PPS MODULE PROVIDED_TARGETS PPS::PPS MODULE_NAME core QMAKE_LIB pps) @@ -397,6 +404,20 @@ int main(void) } ") +# liburing +qt_config_compile_test(liburing + LABEL "liburing" + LIBRARIES uring + CODE +"#include <liburing.h> + +int main(void) +{ + io_uring_enter(0, 0, 0, 0, nullptr); + return 0; +} +") + # linkat qt_config_compile_test(linkat LABEL "linkat()" @@ -584,6 +605,27 @@ int main(void) " ) +qt_config_compile_test(windows_ioring + LABEL "Windows SDK: IORing" + CODE +"#include <windows.h> +#include <ioringapi.h> + +int main(void) +{ + /* BEGIN TEST: */ + IORING_CREATE_FLAGS flags; + memset(&flags, 0, sizeof(flags)); + HIORING ioRingHandle = nullptr; + HRESULT hr = CreateIoRing(IORING_VERSION_3, flags, 1, 1, &ioRingHandle); + if (hr == IORING_E_SUBMISSION_QUEUE_FULL) // not valid, but test that this #define exists + return 0; + /* END TEST: */ + return 0; +} +" +) + # cpp_winrt qt_config_compile_test(cpp_winrt LABEL "cpp/winrt" @@ -764,6 +806,11 @@ qt_feature("winsdkicu" PRIVATE CONDITION TEST_winsdkicu DISABLE QT_FEATURE_icu ) +qt_feature("windows_ioring" PRIVATE + LABEL "Windows I/O Ring" + AUTODETECT WIN32 AND CMAKE_HOST_SYSTEM_VERSION VERSION_GREATER_EQUAL 10.0.22000 + CONDITION TEST_windows_ioring +) qt_feature("inotify" PUBLIC PRIVATE LABEL "inotify" CONDITION TEST_inotify OR TEST_fsnotify @@ -804,6 +851,11 @@ qt_feature("linkat" PRIVATE AUTODETECT ( LINUX AND NOT ANDROID ) OR HURD CONDITION TEST_linkat ) +qt_feature("liburing" PRIVATE + LABEL "liburing" + AUTODETECT LINUX + CONDITION Liburing_FOUND +) qt_feature("std-atomic64" PUBLIC LABEL "64 bit atomic operations" CONDITION WrapAtomic_FOUND @@ -1226,14 +1278,13 @@ qt_feature("permissions" PUBLIC ) qt_feature("openssl-hash" PRIVATE LABEL "OpenSSL based cryptographic hash" - AUTODETECT OFF CONDITION QT_FEATURE_openssl_linked AND QT_FEATURE_opensslv30 PURPOSE "Uses OpenSSL based implementation of cryptographic hash algorithms." ) qt_feature("async-io" PRIVATE LABEL "Async File I/O" PURPOSE "Provides support for asynchronous file I/O." - CONDITION (QT_FEATURE_thread AND QT_FEATURE_future) OR APPLE + CONDITION (QT_FEATURE_thread AND QT_FEATURE_future) OR APPLE OR (LINUX AND QT_FEATURE_liburing) OR (WIN32 AND QT_FEATURE_windows_ioring) ) qt_configure_add_summary_section(NAME "Qt Core") @@ -1245,6 +1296,8 @@ qt_configure_add_summary_entry(ARGS "forkfd_pidfd" CONDITION LINUX) qt_configure_add_summary_entry(ARGS "glib") qt_configure_add_summary_entry(ARGS "icu") qt_configure_add_summary_entry(ARGS "jemalloc") +qt_configure_add_summary_entry(ARGS "liburing") +qt_configure_add_summary_entry(ARGS "windows_ioring") qt_configure_add_summary_entry(ARGS "timezone_tzdb") qt_configure_add_summary_entry(ARGS "system-libb2") qt_configure_add_summary_entry(ARGS "mimetype-database") diff --git a/src/corelib/doc/qtcore.qdocconf b/src/corelib/doc/qtcore.qdocconf index d2b386373a0..b3e4e9d30a9 100644 --- a/src/corelib/doc/qtcore.qdocconf +++ b/src/corelib/doc/qtcore.qdocconf @@ -21,7 +21,7 @@ qhp.QtCore.virtualFolder = qtcore qhp.QtCore.indexTitle = Qt Core qhp.QtCore.indexRoot = -qhp.QtCore.subprojects = manual classes +qhp.QtCore.subprojects = manual examples classes qhp.QtCore.subprojects.manual.title = Qt Core qhp.QtCore.subprojects.manual.indexTitle = Qt Core module topics qhp.QtCore.subprojects.manual.type = manual @@ -31,6 +31,11 @@ qhp.QtCore.subprojects.classes.indexTitle = Qt Core C++ Classes qhp.QtCore.subprojects.classes.selectors = class fake:headerfile qhp.QtCore.subprojects.classes.sortPages = true +qhp.QtCore.subprojects.examples.title = Examples +qhp.QtCore.subprojects.examples.indexTitle = Qt Core Examples +qhp.QtCore.subprojects.examples.selectors = example +qhp.QtCore.subprojects.examples.sortPages = true + tagfile = ../../../doc/qtcore/qtcore.tags # Make QtCore depend on all doc modules; this ensures complete inheritance diff --git a/src/corelib/doc/snippets/CMakeLists.txt b/src/corelib/doc/snippets/CMakeLists.txt index 55db84ccbea..c0d15463e9c 100644 --- a/src/corelib/doc/snippets/CMakeLists.txt +++ b/src/corelib/doc/snippets/CMakeLists.txt @@ -13,6 +13,7 @@ add_library(corelib_snippets OBJECT qmessageauthenticationcode/main.cpp qmetatype/registerConverters.cpp qrangemodel/main.cpp + qrangemodeladapter/main.cpp qstack/main.cpp qstringlist/main.cpp qstringlistmodel/main.cpp diff --git a/src/corelib/doc/snippets/code/doc_src_properties.cpp b/src/corelib/doc/snippets/code/doc_src_properties.cpp index eafa7acda3b..07f574c2de2 100644 --- a/src/corelib/doc/snippets/code/doc_src_properties.cpp +++ b/src/corelib/doc/snippets/code/doc_src_properties.cpp @@ -16,6 +16,8 @@ Q_PROPERTY(type name [BINDABLE bindableProperty] [CONSTANT] [FINAL] + [VIRTUAL] + [OVERRIDE] [REQUIRED]) //! [0] diff --git a/src/corelib/doc/snippets/code/src_corelib_kernel_qvariant.cpp b/src/corelib/doc/snippets/code/src_corelib_kernel_qvariant.cpp index e23f6c9d103..1d7a3fa6409 100644 --- a/src/corelib/doc/snippets/code/src_corelib_kernel_qvariant.cpp +++ b/src/corelib/doc/snippets/code/src_corelib_kernel_qvariant.cpp @@ -7,8 +7,8 @@ #include <QVariant> #include <QColor> #include <QPalette> -#include <QSequentialIterable> -#include <QAssociativeIterable> +#include <QMetaSequence> +#include <QMetaAssociation> QString tr(const char *s) { @@ -125,14 +125,14 @@ QVariant examples() QVariant variant = QVariant::fromValue(intList); if (variant.canConvert<QVariantList>()) { - QSequentialIterable iterable = variant.value<QSequentialIterable>(); + QMetaSequence::Iterable iterable = variant.value<QMetaSequence::Iterable>(); // Can use C++11 range-for: for (const QVariant &v : iterable) { qDebug() << v; } // Can use iterators: - QSequentialIterable::const_iterator it = iterable.begin(); - const QSequentialIterable::const_iterator end = iterable.end(); + QMetaSequence::Iterable::const_iterator it = iterable.begin(); + const QMetaSequence::Iterable::const_iterator end = iterable.end(); for ( ; it != end; ++it) { qDebug() << *it; } @@ -149,14 +149,14 @@ QVariant examples() QVariant variant = QVariant::fromValue(mapping); if (variant.canConvert<QVariantHash>()) { - QAssociativeIterable iterable = variant.value<QAssociativeIterable>(); + QMetaAssociation::Iterable iterable = variant.value<QMetaAssociation::Iterable>(); // Can use C++11 range-for over the values: for (const QVariant &v : iterable) { qDebug() << v; } // Can use iterators: - QAssociativeIterable::const_iterator it = iterable.begin(); - const QAssociativeIterable::const_iterator end = iterable.end(); + QMetaAssociation::Iterable::const_iterator it = iterable.begin(); + const QMetaAssociation::Iterable::const_iterator end = iterable.end(); for ( ; it != end; ++it) { qDebug() << *it; // The current value qDebug() << it.key(); diff --git a/src/corelib/doc/snippets/code/src_corelib_thread_qfuture.cpp b/src/corelib/doc/snippets/code/src_corelib_thread_qfuture.cpp index 6531d025bef..89ac917ccd3 100644 --- a/src/corelib/doc/snippets/code/src_corelib_thread_qfuture.cpp +++ b/src/corelib/doc/snippets/code/src_corelib_thread_qfuture.cpp @@ -543,6 +543,30 @@ void examples(QFuture<QString> someQStringFuture, f.cancelChain(); //! [38] } + + { + auto createFuture = [] { return QtFuture::makeReadyVoidFuture(); }; + auto runNestedComputation = [] { return QtFuture::makeReadyVoidFuture(); }; + //! [39] + QFuture<void> nested; + auto f = createFuture() + .then([&]{ + nested = runNestedComputation(); + // do some other work + return nested; + }) + .unwrap() + .then([]{ + // other continuation + }) + .onCanceled([]{ + // handle cancellation + }); + //... + f.cancelChain(); + nested.cancel(); + //! [39] + } } class SomeClass : public QObject diff --git a/src/corelib/doc/snippets/qrangemodeladapter/main.cpp b/src/corelib/doc/snippets/qrangemodeladapter/main.cpp new file mode 100644 index 00000000000..a0791dd1dd1 --- /dev/null +++ b/src/corelib/doc/snippets/qrangemodeladapter/main.cpp @@ -0,0 +1,366 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include <QtCore/qrangemodeladapter.h> + +#ifndef QT_NO_WIDGETS + +using namespace Qt::StringLiterals; + +#include <QtWidgets/qlistview.h> +#include <QtWidgets/qtableview.h> +#include <QtWidgets/qtreeview.h> +#include <vector> + +class Book +{ + Q_GADGET + Q_PROPERTY(QString title READ title) + Q_PROPERTY(QString author READ author) + Q_PROPERTY(QString summary MEMBER m_summary) + Q_PROPERTY(int rating READ rating WRITE setRating) +public: + enum Roles + { + TitleRole = Qt::UserRole, + AuthorRole, + SummaryRole, + RatingRole, + }; + + Book() = default; + Book(const QString &title, const QString &author); + + // C++ rule of 0: destructor, as well as copy/move operations + // provided by the compiler. + + // read-only properties + QString title() const { return m_title; } + QString author() const { return m_author; } + + // read/writable property with input validation + int rating() const { return m_rating; } + void setRating(int rating) + { + m_rating = qBound(0, rating, 5); + } + +private: + QString m_title; + QString m_author; + QString m_summary; + int m_rating = 0; +}; + +template <> struct QRangeModel::RowOptions<Book> +{ + static constexpr auto rowCategory = QRangeModel::RowCategory::MultiRoleItem; +}; + +struct TreeRow; +using Tree = QList<TreeRow *>; + +struct TreeRow +{ + QString firstColumn; + int secondColumn = 0; + + TreeRow() = default; + explicit TreeRow(const QString &first, int second, std::optional<Tree> children = std::nullopt) + : firstColumn(first), secondColumn(second), m_children(children) + {} + + TreeRow *parentRow() const { return m_parent; } + void setParentRow(TreeRow *parent) { m_parent = parent; } + const std::optional<Tree> &childRows() const { return m_children; } + std::optional<Tree> &childRows() { return m_children; } + +private: + TreeRow *m_parent; + std::optional<Tree> m_children; + + template <size_t I> friend inline decltype(auto) get(const TreeRow &row) { + static_assert(I < 2); + return false; + } +}; + +namespace std { + template<> struct tuple_size<TreeRow> : integral_constant<int, 2> {}; + template<> struct tuple_element<0, TreeRow> { using type = QString; }; + template<> struct tuple_element<1, TreeRow> { using type = int; }; +} + +void construct_and_use() +{ + //! [construct] + std::vector<int> data = {1, 2, 3, 4, 5}; + QRangeModelAdapter adapter(&data); + //! [construct] + + //! [use-model] + QListView listView; + listView.setModel(adapter.model()); + //! [use-model] +} + +void get_and_set() +{ + QListView tableView; + //! [get-range] + QList<Book> books = { + // ... + }; + QRangeModelAdapter adapter(books); + tableView.setModel(adapter.model()); + + // show UI and where the user can modify the list + + QList<Book> modifiedBooks = adapter; + // or + modifiedBooks = adapter.range(); + //! [get-range] + + //! [set-range] + // reset to the original + adapter = books; + // or + adapter.setRange(books); + //! [set-range] +} + +void dataAccess() +{ + int row = 0; + int column = 0; + int path = 0; + int to = 0; + int branch = 0; + + QRangeModelAdapter listAdapter(QList<int>{}); + //! [list-data] + QVariant listItem = listAdapter.data(row); + //! [list-data] + + QRangeModelAdapter tableAdapter(QList<QList<int>>{}); + //! [table-data] + QVariant tableItem = tableAdapter.data(row, column); + //! [table-data] + + QRangeModelAdapter treeAdapter(QList<TreeRow *>{}); + //! [tree-data] + QVariant treeItem = treeAdapter.data({path, to, branch}, column); + //! [tree-data] + + //! [multirole-data] + QRangeModelAdapter listOfBooks(QList<Book>{ + // ~~~ + }); + QString bookTitle = listOfBooks.data(0, Book::TitleRole).toString(); + Book multiRoleItem = listOfBooks.data(0).value<Book>(); + //! [multirole-data] +} + +void list_access() +{ + QListView listView; + { + //! [list-access] + QRangeModelAdapter list(std::vector<int>{1, 2, 3, 4, 5}); + listView.setModel(list.model()); + + int firstValue = list.at(0); // == 1 + list.at(0) = -1; + list.at(1) = list.at(4); + //! [list-access] + } + { + //! [list-access-multirole] + QRangeModelAdapter books(QList<Book>{ + // ~~~ + }); + Book firstBook = books.at(0); + Book newBook = {}; + books.at(0) = newBook; // dataChanged() emitted + //! [list-access-multirole] + + //! [list-access-multirole-member-access] + QString title = books.at(0)->title(); + //! [list-access-multirole-member-access] + + //! [list-access-multirole-write-back] + // books.at(0)->setRating(5); - not possible even though 'books' is not const + firstBook = books.at(0); + firstBook.setRating(5); + books.at(0) = firstBook; // dataChanged() emitted + //! [list-access-multirole-write-back] + } +} + +void table_access() +{ + QTableView tableView; + { + //! [table-item-access] + QRangeModelAdapter table(std::vector<std::vector<double>>{ + {1.0, 2.0, 3.0, 4.0, 5.0}, + {6.0, 7.0, 8.0, 9.0, 10.0}, + }); + tableView.setModel(table.model()); + + double value = table.at(0, 2); // value == 3.0 + table.at(0, 2) = value * 2; // table[0, 2] == 6.0 + //! [table-item-access] + + //! [table-row-const-access] + const auto &constTable = table; + const std::vector<double> &topRow = constTable.at(0); + //! [table-row-const-access] + + //! [table-row-access] + auto lastRow = table.at(table.rowCount() - 1); + lastRow = { 6.5, 7.5, 8.0, 9.0, 10 }; // emits dataChanged() for entire row + //! [table-row-access] + } + + { + //! [table-mixed-type-access] + QRangeModelAdapter table(std::vector<std::tuple<int, QString>>{ + // ~~~ + }); + int number = table.at(0, 0)->toInt(); + QString text = table.at(0, 1)->toString(); + //! [table-mixed-type-access] + } +} + +void tree_access() +{ + QTreeView treeView; + + //! [tree-row-access] + QRangeModelAdapter tree = QRangeModelAdapter(Tree{ + new TreeRow{"Germany", 357002, Tree{ + new TreeRow("Bavaria", 70550) + }, + }, + new TreeRow{"France", 632702}, + }); + treeView.setModel(tree.model()); + + auto germanyData = tree.at(0); + auto bavariaData = tree.at({0, 0}); + //! [tree-row-access] + + //! [tree-item-access] + auto germanyName = tree.at(0, 0); + auto bavariaSize = tree.at({0, 0}, 1); + //! [tree-item-access] + + //! [tree-row-write] + // deletes the old row - tree was moved in + tree.at({0, 0}) = new TreeRow{"Berlin", 892}; + //! [tree-row-write] +} + +void read_only() +{ +#if 0 + //! [read-only] + const QStringList strings = {"On", "Off"}; + QRangeModelAdapter adapter(strings); + adapter.at(0) = "Undecided"; // compile error: return value of 'at' is const + adapter.insertRow(0); // compiler error: requirements not satisfied + //! [read-only] +#endif +} + +void list_iterate() +{ + QRangeModelAdapter books(QList<Book>{ + // ~~~ + }); + QListView view; + view.setModel(books.model()); + + //! [ranged-for-const-list] + for (const auto &book : std::as_const(books)) { + qDebug() << "The book" << book.title() + << "written by" << book.author() + << "has" << book.rating() << "stars"; + } + //! [ranged-for-const-list] + + //! [ranged-for-mutable-list] + for (auto book : books) { + qDebug() << "The book" << book->title() + << "written by" << book->author() + << "has" << book->rating() << "stars"; + + Book copy = book; + copy.setRating(copy.rating() + 1); + book = copy; + } + //! [ranged-for-mutable-list] +} + +void table_iterate() +{ + //! [ranged-for-const-table] + QRangeModelAdapter table(std::vector<std::pair<int, QString>>{ + // ~~~ + }); + + for (const auto &row : std::as_const(table)) { + qDebug() << "Number is" << row->first << "and string is" << row->second; + } + //! [ranged-for-const-table] + + //! [ranged-for-const-table-items] + for (const auto &row : std::as_const(table)) { + for (const auto &item : row) { + qDebug() << item; // item is a QVariant + } + } + //! [ranged-for-const-table-items] + + //! [ranged-for-mutable-table] + for (auto row : table) { + qDebug() << "Number is" << row->first << "and string is" << row->second; + row = std::make_pair(42, u"forty-two"_s); + } + //! [ranged-for-mutable-table] + + //! [ranged-for-mutable-table-items] + for (auto row : table) { + for (auto item : row) { + item = 42; + } + } + //! [ranged-for-mutable-table-items] +} + +void tree_iterate() +{ + QRangeModelAdapter tree = QRangeModelAdapter(Tree{ + new TreeRow{"1", 1, Tree{ + new TreeRow("1.1", 11) + }, + }, + new TreeRow{"2", 2}, + }); + + static_assert(std::is_same_v<typename decltype(tree)::DataReference::value_type, QVariant>); + + //! [ranged-for-tree] + for (auto row : tree) { + if (row.hasChildren()) { + for (auto child : row.children()) { + // ~~~ + } + } + } + //! [ranged-for-tree] +} + +#endif diff --git a/src/corelib/doc/src/cmake/cmake-configure-variables.qdoc b/src/corelib/doc/src/cmake/cmake-configure-variables.qdoc index 0a0dc0b3c50..b8e5e038a33 100644 --- a/src/corelib/doc/src/cmake/cmake-configure-variables.qdoc +++ b/src/corelib/doc/src/cmake/cmake-configure-variables.qdoc @@ -133,7 +133,6 @@ use, for example, library files referenced from a Qt installation. \summary {Forces or disables release package signing regardless of the build type.} \cmakevariablesince 6.7 -\preliminarycmakevariable \cmakevariableandroidonly When set to \c Release, the \c --release flag is passed to the \c @@ -143,6 +142,10 @@ effectively disables release package signing even in Release or RelWithDebInfo builds. When not set, the default behavior is to use release package signing in build types other than Debug. +This variable is not supposed to be set in CMake project files. Rather set it +when configuring your project on the command line or in the CMake settings of +your IDE. + \sa {androiddeployqt} */ diff --git a/src/corelib/doc/src/cmake/qt_add_android_dynamic_feature_java_source_dirs.qdoc b/src/corelib/doc/src/cmake/qt_add_android_dynamic_feature_java_source_dirs.qdoc new file mode 100644 index 00000000000..cf670110cab --- /dev/null +++ b/src/corelib/doc/src/cmake/qt_add_android_dynamic_feature_java_source_dirs.qdoc @@ -0,0 +1,30 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! +\page qt_add_android_dynamic_feature_java_source_dirs.html +\ingroup cmake-commands-qtcore + +\title qt_add_android_dynamic_feature_java_source_dirs +\keyword qt6_add_android_dynamic_feature_java_source_dirs + +\summary {Adds Java source directories to a dynamic feature build.} + +\include cmake-find-package-core.qdocinc + +\cmakecommandsince 6.11 + +\section1 Synopsis + +\badcode +qt_add_android_dynamic_feature_java_source_dirs(target [SOURCE_DIRS <directory1> <directory2> ...]) +\endcode + +\versionlessCMakeCommandsNote qt6_add_android_dynamic_feature_java_source_dirs() + +\section1 Description + +The command adds extra Java/Kotlin source directories to the \c {target} +executable when building the executable app with dynamic feature functionality. +To be used in conjunction with qt6_add_android_dynamic_features(). +*/ diff --git a/src/corelib/doc/src/cmake/qt_add_android_permission.qdoc b/src/corelib/doc/src/cmake/qt_add_android_permission.qdoc index 68fe6a8e5cc..8fa6c2bb6d7 100644 --- a/src/corelib/doc/src/cmake/qt_add_android_permission.qdoc +++ b/src/corelib/doc/src/cmake/qt_add_android_permission.qdoc @@ -14,6 +14,10 @@ \cmakecommandsince 6.9 +\note When using this API, the \c{<!-- %%INSERT_PERMISSIONS -->} tag must be present +in the AndroidManifest.xml. For further information on the use of this tag, +see \l {Qt Permissions and Features} + \section1 Synopsis \badcode diff --git a/src/corelib/doc/src/external-resources.qdoc b/src/corelib/doc/src/external-resources.qdoc index 2232b49bf23..5d357c65496 100644 --- a/src/corelib/doc/src/external-resources.qdoc +++ b/src/corelib/doc/src/external-resources.qdoc @@ -28,11 +28,6 @@ */ /*! - \externalpage https://marcmutz.wordpress.com/effective-qt/containers/#containers-qlist - \title Pros and Cons of Using QList -*/ - -/*! \externalpage https://marcmutz.wordpress.com/effective-qt/containers/ \title Understand the Qt Containers */ diff --git a/src/corelib/doc/src/jni.qdoc b/src/corelib/doc/src/jni.qdoc index d05dd44ff60..72b29506c34 100644 --- a/src/corelib/doc/src/jni.qdoc +++ b/src/corelib/doc/src/jni.qdoc @@ -65,10 +65,10 @@ \endcode The C++ classes \c{QtJniTypes::File} and \c{QtJniTypes::FileWriter} are - then QJniObject-like types that can be used to instantiate the - corresponding Java class, to call methods, and to pass such instances - through QJniObject variadic template methods with automatic, compile-time - signature deduction. + then QJniObject-like types (specializations of QtJniTypes::JObject, to be + precise) that can be used to instantiate the corresponding Java class, to + call methods, and to pass such instances through QJniObject variadic + template methods with automatic, compile-time signature deduction. \code using namespace QtJniTypes; @@ -89,7 +89,7 @@ }); \endcode - \sa Q_DECLARE_JNI_NATIVE_METHOD, Q_JNI_NATIVE_METHOD + \sa Q_DECLARE_JNI_NATIVE_METHOD, Q_JNI_NATIVE_METHOD, QtJniTypes::JObject */ /*! diff --git a/src/corelib/doc/src/objectmodel/properties.qdoc b/src/corelib/doc/src/objectmodel/properties.qdoc index 0e66c8445c2..71e14222763 100644 --- a/src/corelib/doc/src/objectmodel/properties.qdoc +++ b/src/corelib/doc/src/objectmodel/properties.qdoc @@ -128,10 +128,18 @@ constant value may be different for different instances of the object. A constant property cannot have a WRITE method or a NOTIFY signal. - \li The presence of the \c FINAL attribute indicates that the property - will not be overridden by a derived class. This can be used for performance - optimizations in some cases, but is not enforced by moc. Care must be taken - never to override a \c FINAL property. + \li \c FINAL, \c VIRTUAL, \c OVERRIDE modifiers mirror the semantics of their C++ and + \l {Override Semantics}{QML counterparts}, allowing to make property overriding explicit at the + meta-object level. + + \note At present, these modifiers are not enforced by moc. + They are recognized syntactically and are primarily used for QML runtime enforcement and tooling + diagnostics. Future versions may introduce stricter compile-time validation and warnings for + invalid overrides across modules. + + \note If you want to change accessing behaviour for a property, use the + polymorphism provided by C++. + \li The presence of the \c REQUIRED attribute indicates that the property should be set by a user of the class. This is not enforced by moc, and is diff --git a/src/corelib/doc/src/qtcore.qdoc b/src/corelib/doc/src/qtcore.qdoc index ec5fa564639..fbcd02aeea5 100644 --- a/src/corelib/doc/src/qtcore.qdoc +++ b/src/corelib/doc/src/qtcore.qdoc @@ -31,3 +31,12 @@ target_link_libraries(mytarget PRIVATE Qt6::CorePrivate) \endcode */ + +/*! + \group corelib_examples + \title Qt Core Examples + + \brief Examples for the Qt Core. + + To learn how to use features of the Qt Core module, see examples: +*/ diff --git a/src/corelib/global/patches/tlexpected/0006-Namespace-TL_-macros-with-Q23_.patch b/src/corelib/global/patches/tlexpected/0006-Namespace-TL_-macros-with-Q23_.patch new file mode 100644 index 00000000000..f3c45f07594 --- /dev/null +++ b/src/corelib/global/patches/tlexpected/0006-Namespace-TL_-macros-with-Q23_.patch @@ -0,0 +1,914 @@ +From 5676e5f83597dc1fb32b551c863633eff102e879 Mon Sep 17 00:00:00 2001 +From: Marc Mutz <[email protected]> +Date: Thu, 27 Nov 2025 07:51:19 +0100 +Subject: [PATCH] Namespace TL_ macros with Q23_ + +Change-Id: Ib5762ec8ebe81e0c750da84be29531b7179c5025 +--- + src/corelib/global/qexpected_p.h | 312 +++++++++++++++---------------- + 1 file changed, 156 insertions(+), 156 deletions(-) + +diff --git a/src/corelib/global/qexpected_p.h b/src/corelib/global/qexpected_p.h +index 24ea5be1e5e..54bcae51102 100644 +--- a/src/corelib/global/qexpected_p.h ++++ b/src/corelib/global/qexpected_p.h +@@ -16,8 +16,8 @@ + // <http://creativecommons.org/publicdomain/zero/1.0/>. + /// + +-#ifndef TL_EXPECTED_HPP +-#define TL_EXPECTED_HPP ++#ifndef Q23_TL_EXPECTED_HPP ++#define Q23_TL_EXPECTED_HPP + + // + // W A R N I N G +@@ -30,9 +30,9 @@ + // We mean it. + // + +-#define TL_EXPECTED_VERSION_MAJOR 1 +-#define TL_EXPECTED_VERSION_MINOR 1 +-#define TL_EXPECTED_VERSION_PATCH 0 ++#define Q23_TL_EXPECTED_VERSION_MAJOR 1 ++#define Q23_TL_EXPECTED_VERSION_MINOR 1 ++#define Q23_TL_EXPECTED_VERSION_PATCH 0 + + #include <QtCore/private/qglobal_p.h> + #include <QtCore/qassert.h> +@@ -44,45 +44,45 @@ + #include <type_traits> + #include <utility> + +-#define TL_ASSERT Q_ASSERT ++#define Q23_TL_ASSERT Q_ASSERT + + #if defined(__EXCEPTIONS) || defined(_CPPUNWIND) +-#define TL_EXPECTED_EXCEPTIONS_ENABLED ++#define Q23_TL_EXPECTED_EXCEPTIONS_ENABLED + #endif + +-#if defined(TL_EXPECTED_EXCEPTIONS_ENABLED) && defined(QT_NO_EXCEPTIONS) +-# undef TL_EXPECTED_EXCEPTIONS_ENABLED ++#if defined(Q23_TL_EXPECTED_EXCEPTIONS_ENABLED) && defined(QT_NO_EXCEPTIONS) ++# undef Q23_TL_EXPECTED_EXCEPTIONS_ENABLED + #endif + + #if (defined(_MSC_VER) && _MSC_VER == 1900) +-#define TL_EXPECTED_MSVC2015 +-#define TL_EXPECTED_MSVC2015_CONSTEXPR ++#define Q23_TL_EXPECTED_MSVC2015 ++#define Q23_TL_EXPECTED_MSVC2015_CONSTEXPR + #else +-#define TL_EXPECTED_MSVC2015_CONSTEXPR constexpr ++#define Q23_TL_EXPECTED_MSVC2015_CONSTEXPR constexpr + #endif + + #if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 9 && \ + !defined(__clang__)) +-#define TL_EXPECTED_GCC49 ++#define Q23_TL_EXPECTED_GCC49 + #endif + + #if (defined(__GNUC__) && __GNUC__ == 5 && __GNUC_MINOR__ <= 4 && \ + !defined(__clang__)) +-#define TL_EXPECTED_GCC54 ++#define Q23_TL_EXPECTED_GCC54 + #endif + + #if (defined(__GNUC__) && __GNUC__ == 5 && __GNUC_MINOR__ <= 5 && \ + !defined(__clang__)) +-#define TL_EXPECTED_GCC55 ++#define Q23_TL_EXPECTED_GCC55 + #endif + +-#if !defined(TL_ASSERT) ++#if !defined(Q23_TL_ASSERT) + //can't have assert in constexpr in C++11 and GCC 4.9 has a compiler bug +-#if (TL_CPLUSPLUS > 201103L) && !defined(TL_EXPECTED_GCC49) ++#if (Q23_TL_CPLUSPLUS > 201103L) && !defined(Q23_TL_EXPECTED_GCC49) + #include <cassert> +-#define TL_ASSERT(x) assert(x) ++#define Q23_TL_ASSERT(x) assert(x) + #else +-#define TL_ASSERT(x) ++#define Q23_TL_ASSERT(x) + #endif + #endif + +@@ -90,22 +90,22 @@ + !defined(__clang__)) + // GCC < 5 doesn't support overloading on const&& for member functions + +-#define TL_EXPECTED_NO_CONSTRR ++#define Q23_TL_EXPECTED_NO_CONSTRR + // GCC < 5 doesn't support some standard C++11 type traits +-#define TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ ++#define Q23_TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ + std::has_trivial_copy_constructor<T> +-#define TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \ ++#define Q23_TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \ + std::has_trivial_copy_assign<T> + + // This one will be different for GCC 5.7 if it's ever supported +-#define TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T) \ ++#define Q23_TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T) \ + std::is_trivially_destructible<T> + + // GCC 5 < v < 8 has a bug in is_trivially_copy_constructible which breaks + // std::vector for non-copyable types + #elif (defined(__GNUC__) && __GNUC__ < 8 && !defined(__clang__)) +-#ifndef TL_GCC_LESS_8_TRIVIALLY_COPY_CONSTRUCTIBLE_MUTEX +-#define TL_GCC_LESS_8_TRIVIALLY_COPY_CONSTRUCTIBLE_MUTEX ++#ifndef Q23_TL_GCC_LESS_8_TRIVIALLY_COPY_CONSTRUCTIBLE_MUTEX ++#define Q23_TL_GCC_LESS_8_TRIVIALLY_COPY_CONSTRUCTIBLE_MUTEX + QT_BEGIN_NAMESPACE + namespace q23 { + namespace detail { +@@ -121,50 +121,50 @@ struct is_trivially_copy_constructible<std::vector<T, A>> : std::false_type {}; + QT_END_NAMESPACE + #endif + +-#define TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ ++#define Q23_TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ + q23::detail::is_trivially_copy_constructible<T> +-#define TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \ ++#define Q23_TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \ + std::is_trivially_copy_assignable<T> +-#define TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T) \ ++#define Q23_TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T) \ + std::is_trivially_destructible<T> + #else +-#define TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ ++#define Q23_TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ + std::is_trivially_copy_constructible<T> +-#define TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \ ++#define Q23_TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \ + std::is_trivially_copy_assignable<T> +-#define TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T) \ ++#define Q23_TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T) \ + std::is_trivially_destructible<T> + #endif + + #ifdef _MSVC_LANG +-#define TL_CPLUSPLUS _MSVC_LANG ++#define Q23_TL_CPLUSPLUS _MSVC_LANG + #else +-#define TL_CPLUSPLUS __cplusplus ++#define Q23_TL_CPLUSPLUS __cplusplus + #endif + +-#if TL_CPLUSPLUS > 201103L +-#define TL_EXPECTED_CXX14 ++#if Q23_TL_CPLUSPLUS > 201103L ++#define Q23_TL_EXPECTED_CXX14 + #endif + +-#ifdef TL_EXPECTED_GCC49 +-#define TL_EXPECTED_GCC49_CONSTEXPR ++#ifdef Q23_TL_EXPECTED_GCC49 ++#define Q23_TL_EXPECTED_GCC49_CONSTEXPR + #else +-#define TL_EXPECTED_GCC49_CONSTEXPR constexpr ++#define Q23_TL_EXPECTED_GCC49_CONSTEXPR constexpr + #endif + +-#if (TL_CPLUSPLUS == 201103L || defined(TL_EXPECTED_MSVC2015) || \ +- defined(TL_EXPECTED_GCC49)) +-#define TL_EXPECTED_11_CONSTEXPR ++#if (Q23_TL_CPLUSPLUS == 201103L || defined(Q23_TL_EXPECTED_MSVC2015) || \ ++ defined(Q23_TL_EXPECTED_GCC49)) ++#define Q23_TL_EXPECTED_11_CONSTEXPR + #else +-#define TL_EXPECTED_11_CONSTEXPR constexpr ++#define Q23_TL_EXPECTED_11_CONSTEXPR constexpr + #endif + + QT_BEGIN_NAMESPACE + namespace q23 { + template <class T, class E> class expected; + +-#ifndef TL_MONOSTATE_INPLACE_MUTEX +-#define TL_MONOSTATE_INPLACE_MUTEX ++#ifndef Q23_TL_MONOSTATE_INPLACE_MUTEX ++#define Q23_TL_MONOSTATE_INPLACE_MUTEX + class monostate {}; + + struct in_place_t { +@@ -196,8 +196,8 @@ public: + : m_val(l, std::forward<Args>(args)...) {} + + constexpr const E &error() const & { return m_val; } +- TL_EXPECTED_11_CONSTEXPR E &error() & { return m_val; } +- TL_EXPECTED_11_CONSTEXPR E &&error() && { return std::move(m_val); } ++ Q23_TL_EXPECTED_11_CONSTEXPR E &error() & { return m_val; } ++ Q23_TL_EXPECTED_11_CONSTEXPR E &&error() && { return std::move(m_val); } + constexpr const E &&error() const && { return std::move(m_val); } + + private: +@@ -245,8 +245,8 @@ static constexpr unexpect_t unexpect{}; + + namespace detail { + template <typename E> +-[[noreturn]] TL_EXPECTED_11_CONSTEXPR void throw_exception(E &&e) { +-#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED ++[[noreturn]] Q23_TL_EXPECTED_11_CONSTEXPR void throw_exception(E &&e) { ++#ifdef Q23_TL_EXPECTED_EXCEPTIONS_ENABLED + throw std::forward<E>(e); + #else + (void)e; +@@ -258,8 +258,8 @@ template <typename E> + #endif + } + +-#ifndef TL_TRAITS_MUTEX +-#define TL_TRAITS_MUTEX ++#ifndef Q23_TL_TRAITS_MUTEX ++#define Q23_TL_TRAITS_MUTEX + // C++14-style aliases for brevity + template <class T> using remove_const_t = typename std::remove_const<T>::type; + template <class T> +@@ -278,13 +278,13 @@ struct conjunction<B, Bs...> + : std::conditional<bool(B::value), conjunction<Bs...>, B>::type {}; + + #if defined(_LIBCPP_VERSION) && __cplusplus == 201103L +-#define TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND ++#define Q23_TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND + #endif + + // In C++11 mode, there's an issue in libc++'s std::mem_fn + // which results in a hard-error when using it in a noexcept expression + // in some cases. This is a check to workaround the common failing case. +-#ifdef TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND ++#ifdef Q23_TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND + template <class T> + struct is_pointer_to_non_const_member_func : std::false_type {}; + template <class T, class Ret, class... Args> +@@ -315,7 +315,7 @@ template <class T> struct is_const_or_const_ref<T const> : std::true_type {}; + // https://stackoverflow.com/questions/38288042/c11-14-invoke-workaround + template < + typename Fn, typename... Args, +-#ifdef TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND ++#ifdef Q23_TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND + typename = enable_if_t<!(is_pointer_to_non_const_member_func<Fn>::value && + is_const_or_const_ref<Args...>::value)>, + #endif +@@ -574,7 +574,7 @@ template <class T, class E> struct expected_storage_base<T, E, true, true> { + // T is trivial, E is not. + template <class T, class E> struct expected_storage_base<T, E, true, false> { + constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {} +- TL_EXPECTED_MSVC2015_CONSTEXPR expected_storage_base(no_init_t) ++ Q23_TL_EXPECTED_MSVC2015_CONSTEXPR expected_storage_base(no_init_t) + : m_no_init(), m_has_val(false) {} + + template <class... Args, +@@ -675,7 +675,7 @@ template <class E> struct expected_storage_base<void, E, false, true> { + #if __GNUC__ <= 5 + //no constexpr for GCC 4/5 bug + #else +- TL_EXPECTED_MSVC2015_CONSTEXPR ++ Q23_TL_EXPECTED_MSVC2015_CONSTEXPR + #endif + expected_storage_base() : m_has_val(true) {} + +@@ -770,7 +770,7 @@ struct expected_operations_base : expected_storage_base<T, E> { + this->m_has_val = false; + } + +-#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED ++#ifdef Q23_TL_EXPECTED_EXCEPTIONS_ENABLED + + // These assign overloads ensure that the most efficient assignment + // implementation is used while maintaining the strong exception guarantee. +@@ -820,7 +820,7 @@ struct expected_operations_base : expected_storage_base<T, E> { + auto tmp = std::move(geterr()); + geterr().~unexpected<E>(); + +-#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED ++#ifdef Q23_TL_EXPECTED_EXCEPTIONS_ENABLED + try { + construct(rhs.get()); + } catch (...) { +@@ -855,7 +855,7 @@ struct expected_operations_base : expected_storage_base<T, E> { + if (!this->m_has_val && rhs.m_has_val) { + auto tmp = std::move(geterr()); + geterr().~unexpected<E>(); +-#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED ++#ifdef Q23_TL_EXPECTED_EXCEPTIONS_ENABLED + try { + construct(std::move(rhs).get()); + } catch (...) { +@@ -911,27 +911,27 @@ struct expected_operations_base : expected_storage_base<T, E> { + + bool has_value() const { return this->m_has_val; } + +- TL_EXPECTED_11_CONSTEXPR T &get() & { return this->m_val; } ++ Q23_TL_EXPECTED_11_CONSTEXPR T &get() & { return this->m_val; } + constexpr const T &get() const & { return this->m_val; } +- TL_EXPECTED_11_CONSTEXPR T &&get() && { return std::move(this->m_val); } +-#ifndef TL_EXPECTED_NO_CONSTRR ++ Q23_TL_EXPECTED_11_CONSTEXPR T &&get() && { return std::move(this->m_val); } ++#ifndef Q23_TL_EXPECTED_NO_CONSTRR + constexpr const T &&get() const && { return std::move(this->m_val); } + #endif + +- TL_EXPECTED_11_CONSTEXPR unexpected<E> &geterr() & { ++ Q23_TL_EXPECTED_11_CONSTEXPR unexpected<E> &geterr() & { + return this->m_unexpect; + } + constexpr const unexpected<E> &geterr() const & { return this->m_unexpect; } +- TL_EXPECTED_11_CONSTEXPR unexpected<E> &&geterr() && { ++ Q23_TL_EXPECTED_11_CONSTEXPR unexpected<E> &&geterr() && { + return std::move(this->m_unexpect); + } +-#ifndef TL_EXPECTED_NO_CONSTRR ++#ifndef Q23_TL_EXPECTED_NO_CONSTRR + constexpr const unexpected<E> &&geterr() const && { + return std::move(this->m_unexpect); + } + #endif + +- TL_EXPECTED_11_CONSTEXPR void destroy_val() { get().~T(); } ++ Q23_TL_EXPECTED_11_CONSTEXPR void destroy_val() { get().~T(); } + }; + + // This base class provides some handy member functions which can be used in +@@ -971,20 +971,20 @@ struct expected_operations_base<void, E> : expected_storage_base<void, E> { + + bool has_value() const { return this->m_has_val; } + +- TL_EXPECTED_11_CONSTEXPR unexpected<E> &geterr() & { ++ Q23_TL_EXPECTED_11_CONSTEXPR unexpected<E> &geterr() & { + return this->m_unexpect; + } + constexpr const unexpected<E> &geterr() const & { return this->m_unexpect; } +- TL_EXPECTED_11_CONSTEXPR unexpected<E> &&geterr() && { ++ Q23_TL_EXPECTED_11_CONSTEXPR unexpected<E> &&geterr() && { + return std::move(this->m_unexpect); + } +-#ifndef TL_EXPECTED_NO_CONSTRR ++#ifndef Q23_TL_EXPECTED_NO_CONSTRR + constexpr const unexpected<E> &&geterr() const && { + return std::move(this->m_unexpect); + } + #endif + +- TL_EXPECTED_11_CONSTEXPR void destroy_val() { ++ Q23_TL_EXPECTED_11_CONSTEXPR void destroy_val() { + // no-op + } + }; +@@ -992,8 +992,8 @@ struct expected_operations_base<void, E> : expected_storage_base<void, E> { + // This class manages conditionally having a trivial copy constructor + // This specialization is for when T and E are trivially copy constructible + template <class T, class E, +- bool = is_void_or<T, TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T)>:: +- value &&TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(E)::value, ++ bool = is_void_or<T, Q23_TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T)>:: ++ value &&Q23_TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(E)::value, + bool = (is_copy_constructible_or_void<T>::value && + std::is_copy_constructible<E>::value)> + struct expected_copy_base : expected_operations_base<T, E> { +@@ -1025,7 +1025,7 @@ struct expected_copy_base<T, E, false, true> : expected_operations_base<T, E> { + // doesn't implement an analogue to std::is_trivially_move_constructible. We + // have to make do with a non-trivial move constructor even if T is trivially + // move constructible +-#ifndef TL_EXPECTED_GCC49 ++#ifndef Q23_TL_EXPECTED_GCC49 + template <class T, class E, + bool = is_void_or<T, std::is_trivially_move_constructible<T>>::value + &&std::is_trivially_move_constructible<E>::value> +@@ -1058,12 +1058,12 @@ struct expected_move_base<T, E, false> : expected_copy_base<T, E> { + // This class manages conditionally having a trivial copy assignment operator + template <class T, class E, + bool = is_void_or< +- T, conjunction<TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T), +- TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T), +- TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T)>>::value +- &&TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(E)::value +- &&TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(E)::value +- &&TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(E)::value, ++ T, conjunction<Q23_TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T), ++ Q23_TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T), ++ Q23_TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T)>>::value ++ &&Q23_TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(E)::value ++ &&Q23_TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(E)::value ++ &&Q23_TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(E)::value, + bool = (is_copy_constructible_or_void<T>::value && + std::is_copy_constructible<E>::value && + is_copy_assignable_or_void<T>::value && +@@ -1093,7 +1093,7 @@ struct expected_copy_assign_base<T, E, false, true> : expected_move_base<T, E> { + // doesn't implement an analogue to std::is_trivially_move_assignable. We have + // to make do with a non-trivial move assignment operator even if T is trivially + // move assignable +-#ifndef TL_EXPECTED_GCC49 ++#ifndef Q23_TL_EXPECTED_GCC49 + template <class T, class E, + bool = + is_void_or<T, conjunction<std::is_trivially_destructible<T>, +@@ -1330,10 +1330,10 @@ class expected : private detail::expected_move_assign_base<T, E>, + + template <class U = T, + detail::enable_if_t<!std::is_void<U>::value> * = nullptr> +- TL_EXPECTED_11_CONSTEXPR U &val() { ++ Q23_TL_EXPECTED_11_CONSTEXPR U &val() { + return this->m_val; + } +- TL_EXPECTED_11_CONSTEXPR unexpected<E> &err() { return this->m_unexpect; } ++ Q23_TL_EXPECTED_11_CONSTEXPR unexpected<E> &err() { return this->m_unexpect; } + + template <class U = T, + detail::enable_if_t<!std::is_void<U>::value> * = nullptr> +@@ -1350,19 +1350,19 @@ public: + typedef E error_type; + typedef unexpected<E> unexpected_type; + +-#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ +- !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) +- template <class F> TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) & { ++#if defined(Q23_TL_EXPECTED_CXX14) && !defined(Q23_TL_EXPECTED_GCC49) && \ ++ !defined(Q23_TL_EXPECTED_GCC54) && !defined(Q23_TL_EXPECTED_GCC55) ++ template <class F> Q23_TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) & { + return and_then_impl(*this, std::forward<F>(f)); + } +- template <class F> TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) && { ++ template <class F> Q23_TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) && { + return and_then_impl(std::move(*this), std::forward<F>(f)); + } + template <class F> constexpr auto and_then(F &&f) const & { + return and_then_impl(*this, std::forward<F>(f)); + } + +-#ifndef TL_EXPECTED_NO_CONSTRR ++#ifndef Q23_TL_EXPECTED_NO_CONSTRR + template <class F> constexpr auto and_then(F &&f) const && { + return and_then_impl(std::move(*this), std::forward<F>(f)); + } +@@ -1370,13 +1370,13 @@ public: + + #else + template <class F> +- TL_EXPECTED_11_CONSTEXPR auto ++ Q23_TL_EXPECTED_11_CONSTEXPR auto + and_then(F &&f) & -> decltype(and_then_impl(std::declval<expected &>(), + std::forward<F>(f))) { + return and_then_impl(*this, std::forward<F>(f)); + } + template <class F> +- TL_EXPECTED_11_CONSTEXPR auto ++ Q23_TL_EXPECTED_11_CONSTEXPR auto + and_then(F &&f) && -> decltype(and_then_impl(std::declval<expected &&>(), + std::forward<F>(f))) { + return and_then_impl(std::move(*this), std::forward<F>(f)); +@@ -1387,7 +1387,7 @@ public: + return and_then_impl(*this, std::forward<F>(f)); + } + +-#ifndef TL_EXPECTED_NO_CONSTRR ++#ifndef Q23_TL_EXPECTED_NO_CONSTRR + template <class F> + constexpr auto and_then(F &&f) const && -> decltype(and_then_impl( + std::declval<expected const &&>(), std::forward<F>(f))) { +@@ -1396,12 +1396,12 @@ public: + #endif + #endif + +-#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ +- !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) +- template <class F> TL_EXPECTED_11_CONSTEXPR auto map(F &&f) & { ++#if defined(Q23_TL_EXPECTED_CXX14) && !defined(Q23_TL_EXPECTED_GCC49) && \ ++ !defined(Q23_TL_EXPECTED_GCC54) && !defined(Q23_TL_EXPECTED_GCC55) ++ template <class F> Q23_TL_EXPECTED_11_CONSTEXPR auto map(F &&f) & { + return expected_map_impl(*this, std::forward<F>(f)); + } +- template <class F> TL_EXPECTED_11_CONSTEXPR auto map(F &&f) && { ++ template <class F> Q23_TL_EXPECTED_11_CONSTEXPR auto map(F &&f) && { + return expected_map_impl(std::move(*this), std::forward<F>(f)); + } + template <class F> constexpr auto map(F &&f) const & { +@@ -1412,13 +1412,13 @@ public: + } + #else + template <class F> +- TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl( ++ Q23_TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl( + std::declval<expected &>(), std::declval<F &&>())) + map(F &&f) & { + return expected_map_impl(*this, std::forward<F>(f)); + } + template <class F> +- TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl(std::declval<expected>(), ++ Q23_TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl(std::declval<expected>(), + std::declval<F &&>())) + map(F &&f) && { + return expected_map_impl(std::move(*this), std::forward<F>(f)); +@@ -1430,7 +1430,7 @@ public: + return expected_map_impl(*this, std::forward<F>(f)); + } + +-#ifndef TL_EXPECTED_NO_CONSTRR ++#ifndef Q23_TL_EXPECTED_NO_CONSTRR + template <class F> + constexpr decltype(expected_map_impl(std::declval<const expected &&>(), + std::declval<F &&>())) +@@ -1440,12 +1440,12 @@ public: + #endif + #endif + +-#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ +- !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) +- template <class F> TL_EXPECTED_11_CONSTEXPR auto transform(F &&f) & { ++#if defined(Q23_TL_EXPECTED_CXX14) && !defined(Q23_TL_EXPECTED_GCC49) && \ ++ !defined(Q23_TL_EXPECTED_GCC54) && !defined(Q23_TL_EXPECTED_GCC55) ++ template <class F> Q23_TL_EXPECTED_11_CONSTEXPR auto transform(F &&f) & { + return expected_map_impl(*this, std::forward<F>(f)); + } +- template <class F> TL_EXPECTED_11_CONSTEXPR auto transform(F &&f) && { ++ template <class F> Q23_TL_EXPECTED_11_CONSTEXPR auto transform(F &&f) && { + return expected_map_impl(std::move(*this), std::forward<F>(f)); + } + template <class F> constexpr auto transform(F &&f) const & { +@@ -1456,13 +1456,13 @@ public: + } + #else + template <class F> +- TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl( ++ Q23_TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl( + std::declval<expected &>(), std::declval<F &&>())) + transform(F &&f) & { + return expected_map_impl(*this, std::forward<F>(f)); + } + template <class F> +- TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl(std::declval<expected>(), ++ Q23_TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl(std::declval<expected>(), + std::declval<F &&>())) + transform(F &&f) && { + return expected_map_impl(std::move(*this), std::forward<F>(f)); +@@ -1474,7 +1474,7 @@ public: + return expected_map_impl(*this, std::forward<F>(f)); + } + +-#ifndef TL_EXPECTED_NO_CONSTRR ++#ifndef Q23_TL_EXPECTED_NO_CONSTRR + template <class F> + constexpr decltype(expected_map_impl(std::declval<const expected &&>(), + std::declval<F &&>())) +@@ -1484,12 +1484,12 @@ public: + #endif + #endif + +-#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ +- !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) +- template <class F> TL_EXPECTED_11_CONSTEXPR auto map_error(F &&f) & { ++#if defined(Q23_TL_EXPECTED_CXX14) && !defined(Q23_TL_EXPECTED_GCC49) && \ ++ !defined(Q23_TL_EXPECTED_GCC54) && !defined(Q23_TL_EXPECTED_GCC55) ++ template <class F> Q23_TL_EXPECTED_11_CONSTEXPR auto map_error(F &&f) & { + return map_error_impl(*this, std::forward<F>(f)); + } +- template <class F> TL_EXPECTED_11_CONSTEXPR auto map_error(F &&f) && { ++ template <class F> Q23_TL_EXPECTED_11_CONSTEXPR auto map_error(F &&f) && { + return map_error_impl(std::move(*this), std::forward<F>(f)); + } + template <class F> constexpr auto map_error(F &&f) const & { +@@ -1500,13 +1500,13 @@ public: + } + #else + template <class F> +- TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval<expected &>(), ++ Q23_TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval<expected &>(), + std::declval<F &&>())) + map_error(F &&f) & { + return map_error_impl(*this, std::forward<F>(f)); + } + template <class F> +- TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval<expected &&>(), ++ Q23_TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval<expected &&>(), + std::declval<F &&>())) + map_error(F &&f) && { + return map_error_impl(std::move(*this), std::forward<F>(f)); +@@ -1518,7 +1518,7 @@ public: + return map_error_impl(*this, std::forward<F>(f)); + } + +-#ifndef TL_EXPECTED_NO_CONSTRR ++#ifndef Q23_TL_EXPECTED_NO_CONSTRR + template <class F> + constexpr decltype(map_error_impl(std::declval<const expected &&>(), + std::declval<F &&>())) +@@ -1527,12 +1527,12 @@ public: + } + #endif + #endif +-#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ +- !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) +- template <class F> TL_EXPECTED_11_CONSTEXPR auto transform_error(F &&f) & { ++#if defined(Q23_TL_EXPECTED_CXX14) && !defined(Q23_TL_EXPECTED_GCC49) && \ ++ !defined(Q23_TL_EXPECTED_GCC54) && !defined(Q23_TL_EXPECTED_GCC55) ++ template <class F> Q23_TL_EXPECTED_11_CONSTEXPR auto transform_error(F &&f) & { + return map_error_impl(*this, std::forward<F>(f)); + } +- template <class F> TL_EXPECTED_11_CONSTEXPR auto transform_error(F &&f) && { ++ template <class F> Q23_TL_EXPECTED_11_CONSTEXPR auto transform_error(F &&f) && { + return map_error_impl(std::move(*this), std::forward<F>(f)); + } + template <class F> constexpr auto transform_error(F &&f) const & { +@@ -1543,13 +1543,13 @@ public: + } + #else + template <class F> +- TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval<expected &>(), ++ Q23_TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval<expected &>(), + std::declval<F &&>())) + transform_error(F &&f) & { + return map_error_impl(*this, std::forward<F>(f)); + } + template <class F> +- TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval<expected &&>(), ++ Q23_TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval<expected &&>(), + std::declval<F &&>())) + transform_error(F &&f) && { + return map_error_impl(std::move(*this), std::forward<F>(f)); +@@ -1561,7 +1561,7 @@ public: + return map_error_impl(*this, std::forward<F>(f)); + } + +-#ifndef TL_EXPECTED_NO_CONSTRR ++#ifndef Q23_TL_EXPECTED_NO_CONSTRR + template <class F> + constexpr decltype(map_error_impl(std::declval<const expected &&>(), + std::declval<F &&>())) +@@ -1570,11 +1570,11 @@ public: + } + #endif + #endif +- template <class F> expected TL_EXPECTED_11_CONSTEXPR or_else(F &&f) & { ++ template <class F> expected Q23_TL_EXPECTED_11_CONSTEXPR or_else(F &&f) & { + return or_else_impl(*this, std::forward<F>(f)); + } + +- template <class F> expected TL_EXPECTED_11_CONSTEXPR or_else(F &&f) && { ++ template <class F> expected Q23_TL_EXPECTED_11_CONSTEXPR or_else(F &&f) && { + return or_else_impl(std::move(*this), std::forward<F>(f)); + } + +@@ -1582,7 +1582,7 @@ public: + return or_else_impl(*this, std::forward<F>(f)); + } + +-#ifndef TL_EXPECTED_NO_CONSTRR ++#ifndef Q23_TL_EXPECTED_NO_CONSTRR + template <class F> expected constexpr or_else(F &&f) const && { + return or_else_impl(std::move(*this), std::forward<F>(f)); + } +@@ -1664,7 +1664,7 @@ public: + nullptr, + detail::expected_enable_from_other<T, E, U, G, const U &, const G &> + * = nullptr> +- explicit TL_EXPECTED_11_CONSTEXPR expected(const expected<U, G> &rhs) ++ explicit Q23_TL_EXPECTED_11_CONSTEXPR expected(const expected<U, G> &rhs) + : ctor_base(detail::default_constructor_tag{}) { + if (rhs.has_value()) { + this->construct(*rhs); +@@ -1679,7 +1679,7 @@ public: + nullptr, + detail::expected_enable_from_other<T, E, U, G, const U &, const G &> + * = nullptr> +- TL_EXPECTED_11_CONSTEXPR expected(const expected<U, G> &rhs) ++ Q23_TL_EXPECTED_11_CONSTEXPR expected(const expected<U, G> &rhs) + : ctor_base(detail::default_constructor_tag{}) { + if (rhs.has_value()) { + this->construct(*rhs); +@@ -1693,7 +1693,7 @@ public: + detail::enable_if_t<!(std::is_convertible<U &&, T>::value && + std::is_convertible<G &&, E>::value)> * = nullptr, + detail::expected_enable_from_other<T, E, U, G, U &&, G &&> * = nullptr> +- explicit TL_EXPECTED_11_CONSTEXPR expected(expected<U, G> &&rhs) ++ explicit Q23_TL_EXPECTED_11_CONSTEXPR expected(expected<U, G> &&rhs) + : ctor_base(detail::default_constructor_tag{}) { + if (rhs.has_value()) { + this->construct(std::move(*rhs)); +@@ -1707,7 +1707,7 @@ public: + detail::enable_if_t<(std::is_convertible<U &&, T>::value && + std::is_convertible<G &&, E>::value)> * = nullptr, + detail::expected_enable_from_other<T, E, U, G, U &&, G &&> * = nullptr> +- TL_EXPECTED_11_CONSTEXPR expected(expected<U, G> &&rhs) ++ Q23_TL_EXPECTED_11_CONSTEXPR expected(expected<U, G> &&rhs) + : ctor_base(detail::default_constructor_tag{}) { + if (rhs.has_value()) { + this->construct(std::move(*rhs)); +@@ -1720,14 +1720,14 @@ public: + class U = T, + detail::enable_if_t<!std::is_convertible<U &&, T>::value> * = nullptr, + detail::expected_enable_forward_value<T, E, U> * = nullptr> +- explicit TL_EXPECTED_MSVC2015_CONSTEXPR expected(U &&v) ++ explicit Q23_TL_EXPECTED_MSVC2015_CONSTEXPR expected(U &&v) + : expected(in_place, std::forward<U>(v)) {} + + template < + class U = T, + detail::enable_if_t<std::is_convertible<U &&, T>::value> * = nullptr, + detail::expected_enable_forward_value<T, E, U> * = nullptr> +- TL_EXPECTED_MSVC2015_CONSTEXPR expected(U &&v) ++ Q23_TL_EXPECTED_MSVC2015_CONSTEXPR expected(U &&v) + : expected(in_place, std::forward<U>(v)) {} + + template < +@@ -1773,7 +1773,7 @@ public: + auto tmp = std::move(err()); + err().~unexpected<E>(); + +-#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED ++#ifdef Q23_TL_EXPECTED_EXCEPTIONS_ENABLED + try { + ::new (valptr()) T(std::forward<U>(v)); + this->m_has_val = true; +@@ -1842,7 +1842,7 @@ public: + auto tmp = std::move(err()); + err().~unexpected<E>(); + +-#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED ++#ifdef Q23_TL_EXPECTED_EXCEPTIONS_ENABLED + try { + ::new (valptr()) T(std::forward<Args>(args)...); + this->m_has_val = true; +@@ -1882,7 +1882,7 @@ public: + auto tmp = std::move(err()); + err().~unexpected<E>(); + +-#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED ++#ifdef Q23_TL_EXPECTED_EXCEPTIONS_ENABLED + try { + ::new (valptr()) T(il, std::forward<Args>(args)...); + this->m_has_val = true; +@@ -1943,7 +1943,7 @@ private: + move_constructing_e_can_throw) { + auto temp = std::move(val()); + val().~T(); +-#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED ++#ifdef Q23_TL_EXPECTED_EXCEPTIONS_ENABLED + try { + ::new (errptr()) unexpected_type(std::move(rhs.err())); + rhs.err().~unexpected_type(); +@@ -1966,7 +1966,7 @@ private: + e_is_nothrow_move_constructible) { + auto temp = std::move(rhs.err()); + rhs.err().~unexpected_type(); +-#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED ++#ifdef Q23_TL_EXPECTED_EXCEPTIONS_ENABLED + try { + ::new (rhs.valptr()) T(std::move(val())); + val().~T(); +@@ -2008,36 +2008,36 @@ public: + } + + constexpr const T *operator->() const { +- TL_ASSERT(has_value()); ++ Q23_TL_ASSERT(has_value()); + return valptr(); + } +- TL_EXPECTED_11_CONSTEXPR T *operator->() { +- TL_ASSERT(has_value()); ++ Q23_TL_EXPECTED_11_CONSTEXPR T *operator->() { ++ Q23_TL_ASSERT(has_value()); + return valptr(); + } + + template <class U = T, + detail::enable_if_t<!std::is_void<U>::value> * = nullptr> + constexpr const U &operator*() const & { +- TL_ASSERT(has_value()); ++ Q23_TL_ASSERT(has_value()); + return val(); + } + template <class U = T, + detail::enable_if_t<!std::is_void<U>::value> * = nullptr> +- TL_EXPECTED_11_CONSTEXPR U &operator*() & { +- TL_ASSERT(has_value()); ++ Q23_TL_EXPECTED_11_CONSTEXPR U &operator*() & { ++ Q23_TL_ASSERT(has_value()); + return val(); + } + template <class U = T, + detail::enable_if_t<!std::is_void<U>::value> * = nullptr> + constexpr const U &&operator*() const && { +- TL_ASSERT(has_value()); ++ Q23_TL_ASSERT(has_value()); + return std::move(val()); + } + template <class U = T, + detail::enable_if_t<!std::is_void<U>::value> * = nullptr> +- TL_EXPECTED_11_CONSTEXPR U &&operator*() && { +- TL_ASSERT(has_value()); ++ Q23_TL_EXPECTED_11_CONSTEXPR U &&operator*() && { ++ Q23_TL_ASSERT(has_value()); + return std::move(val()); + } + +@@ -2046,47 +2046,47 @@ public: + + template <class U = T, + detail::enable_if_t<!std::is_void<U>::value> * = nullptr> +- TL_EXPECTED_11_CONSTEXPR const U &value() const & { ++ Q23_TL_EXPECTED_11_CONSTEXPR const U &value() const & { + if (!has_value()) + detail::throw_exception(bad_expected_access<E>(err().error())); + return val(); + } + template <class U = T, + detail::enable_if_t<!std::is_void<U>::value> * = nullptr> +- TL_EXPECTED_11_CONSTEXPR U &value() & { ++ Q23_TL_EXPECTED_11_CONSTEXPR U &value() & { + if (!has_value()) + detail::throw_exception(bad_expected_access<E>(err().error())); + return val(); + } + template <class U = T, + detail::enable_if_t<!std::is_void<U>::value> * = nullptr> +- TL_EXPECTED_11_CONSTEXPR const U &&value() const && { ++ Q23_TL_EXPECTED_11_CONSTEXPR const U &&value() const && { + if (!has_value()) + detail::throw_exception(bad_expected_access<E>(std::move(err()).error())); + return std::move(val()); + } + template <class U = T, + detail::enable_if_t<!std::is_void<U>::value> * = nullptr> +- TL_EXPECTED_11_CONSTEXPR U &&value() && { ++ Q23_TL_EXPECTED_11_CONSTEXPR U &&value() && { + if (!has_value()) + detail::throw_exception(bad_expected_access<E>(std::move(err()).error())); + return std::move(val()); + } + + constexpr const E &error() const & { +- TL_ASSERT(!has_value()); ++ Q23_TL_ASSERT(!has_value()); + return err().error(); + } +- TL_EXPECTED_11_CONSTEXPR E &error() & { +- TL_ASSERT(!has_value()); ++ Q23_TL_EXPECTED_11_CONSTEXPR E &error() & { ++ Q23_TL_ASSERT(!has_value()); + return err().error(); + } + constexpr const E &&error() const && { +- TL_ASSERT(!has_value()); ++ Q23_TL_ASSERT(!has_value()); + return std::move(err().error()); + } +- TL_EXPECTED_11_CONSTEXPR E &&error() && { +- TL_ASSERT(!has_value()); ++ Q23_TL_EXPECTED_11_CONSTEXPR E &&error() && { ++ Q23_TL_ASSERT(!has_value()); + return std::move(err().error()); + } + +@@ -2096,7 +2096,7 @@ public: + "T must be copy-constructible and convertible to from U&&"); + return bool(*this) ? **this : static_cast<T>(std::forward<U>(v)); + } +- template <class U> TL_EXPECTED_11_CONSTEXPR T value_or(U &&v) && { ++ template <class U> Q23_TL_EXPECTED_11_CONSTEXPR T value_or(U &&v) && { + static_assert(std::is_move_constructible<T>::value && + std::is_convertible<U &&, T>::value, + "T must be move-constructible and convertible to from U&&"); +@@ -2109,7 +2109,7 @@ template <class Exp> using exp_t = typename detail::decay_t<Exp>::value_type; + template <class Exp> using err_t = typename detail::decay_t<Exp>::error_type; + template <class Exp, class Ret> using ret_t = expected<Ret, err_t<Exp>>; + +-#ifdef TL_EXPECTED_CXX14 ++#ifdef Q23_TL_EXPECTED_CXX14 + template <class Exp, class F, + detail::enable_if_t<!std::is_void<exp_t<Exp>>::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval<F>(), +@@ -2156,7 +2156,7 @@ constexpr auto and_then_impl(Exp &&exp, F &&f) -> Ret { + } + #endif + +-#ifdef TL_EXPECTED_CXX14 ++#ifdef Q23_TL_EXPECTED_CXX14 + template <class Exp, class F, + detail::enable_if_t<!std::is_void<exp_t<Exp>>::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval<F>(), +@@ -2266,8 +2266,8 @@ auto expected_map_impl(Exp &&exp, F &&f) -> expected<void, err_t<Exp>> { + } + #endif + +-#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ +- !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) ++#if defined(Q23_TL_EXPECTED_CXX14) && !defined(Q23_TL_EXPECTED_GCC49) && \ ++ !defined(Q23_TL_EXPECTED_GCC54) && !defined(Q23_TL_EXPECTED_GCC55) + template <class Exp, class F, + detail::enable_if_t<!std::is_void<exp_t<Exp>>::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval<F>(), +@@ -2382,7 +2382,7 @@ auto map_error_impl(Exp &&exp, F &&f) -> expected<exp_t<Exp>, monostate> { + } + #endif + +-#ifdef TL_EXPECTED_CXX14 ++#ifdef Q23_TL_EXPECTED_CXX14 + template <class Exp, class F, + class Ret = decltype(detail::invoke(std::declval<F>(), + std::declval<Exp>().error())), +-- +2.25.1 + diff --git a/src/corelib/global/qcompilerdetection.h b/src/corelib/global/qcompilerdetection.h index 7bb2e5b04ef..0b42af7686c 100644 --- a/src/corelib/global/qcompilerdetection.h +++ b/src/corelib/global/qcompilerdetection.h @@ -1446,7 +1446,7 @@ QT_WARNING_DISABLE_MSVC(4355) /* 'this' : used in base member initializer list * QT_WARNING_DISABLE_MSVC(4710) /* function not inlined */ QT_WARNING_DISABLE_MSVC(4530) /* C++ exception handler used, but unwind semantics are not enabled. Specify /EHsc */ # elif defined(Q_CC_CLANG_ONLY) -# if Q_CC_CLANG >= 2100 +# if __has_warning("-Wcharacter-conversion") QT_WARNING_DISABLE_CLANG("-Wcharacter-conversion") /* until https://github.com/llvm/llvm-project/issues/163719 is fixed */ # endif # elif defined(Q_CC_BOR) diff --git a/src/corelib/global/qexpected_p.h b/src/corelib/global/qexpected_p.h index 24ea5be1e5e..54bcae51102 100644 --- a/src/corelib/global/qexpected_p.h +++ b/src/corelib/global/qexpected_p.h @@ -16,8 +16,8 @@ // <http://creativecommons.org/publicdomain/zero/1.0/>. /// -#ifndef TL_EXPECTED_HPP -#define TL_EXPECTED_HPP +#ifndef Q23_TL_EXPECTED_HPP +#define Q23_TL_EXPECTED_HPP // // W A R N I N G @@ -30,9 +30,9 @@ // We mean it. // -#define TL_EXPECTED_VERSION_MAJOR 1 -#define TL_EXPECTED_VERSION_MINOR 1 -#define TL_EXPECTED_VERSION_PATCH 0 +#define Q23_TL_EXPECTED_VERSION_MAJOR 1 +#define Q23_TL_EXPECTED_VERSION_MINOR 1 +#define Q23_TL_EXPECTED_VERSION_PATCH 0 #include <QtCore/private/qglobal_p.h> #include <QtCore/qassert.h> @@ -44,45 +44,45 @@ #include <type_traits> #include <utility> -#define TL_ASSERT Q_ASSERT +#define Q23_TL_ASSERT Q_ASSERT #if defined(__EXCEPTIONS) || defined(_CPPUNWIND) -#define TL_EXPECTED_EXCEPTIONS_ENABLED +#define Q23_TL_EXPECTED_EXCEPTIONS_ENABLED #endif -#if defined(TL_EXPECTED_EXCEPTIONS_ENABLED) && defined(QT_NO_EXCEPTIONS) -# undef TL_EXPECTED_EXCEPTIONS_ENABLED +#if defined(Q23_TL_EXPECTED_EXCEPTIONS_ENABLED) && defined(QT_NO_EXCEPTIONS) +# undef Q23_TL_EXPECTED_EXCEPTIONS_ENABLED #endif #if (defined(_MSC_VER) && _MSC_VER == 1900) -#define TL_EXPECTED_MSVC2015 -#define TL_EXPECTED_MSVC2015_CONSTEXPR +#define Q23_TL_EXPECTED_MSVC2015 +#define Q23_TL_EXPECTED_MSVC2015_CONSTEXPR #else -#define TL_EXPECTED_MSVC2015_CONSTEXPR constexpr +#define Q23_TL_EXPECTED_MSVC2015_CONSTEXPR constexpr #endif #if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 9 && \ !defined(__clang__)) -#define TL_EXPECTED_GCC49 +#define Q23_TL_EXPECTED_GCC49 #endif #if (defined(__GNUC__) && __GNUC__ == 5 && __GNUC_MINOR__ <= 4 && \ !defined(__clang__)) -#define TL_EXPECTED_GCC54 +#define Q23_TL_EXPECTED_GCC54 #endif #if (defined(__GNUC__) && __GNUC__ == 5 && __GNUC_MINOR__ <= 5 && \ !defined(__clang__)) -#define TL_EXPECTED_GCC55 +#define Q23_TL_EXPECTED_GCC55 #endif -#if !defined(TL_ASSERT) +#if !defined(Q23_TL_ASSERT) //can't have assert in constexpr in C++11 and GCC 4.9 has a compiler bug -#if (TL_CPLUSPLUS > 201103L) && !defined(TL_EXPECTED_GCC49) +#if (Q23_TL_CPLUSPLUS > 201103L) && !defined(Q23_TL_EXPECTED_GCC49) #include <cassert> -#define TL_ASSERT(x) assert(x) +#define Q23_TL_ASSERT(x) assert(x) #else -#define TL_ASSERT(x) +#define Q23_TL_ASSERT(x) #endif #endif @@ -90,22 +90,22 @@ !defined(__clang__)) // GCC < 5 doesn't support overloading on const&& for member functions -#define TL_EXPECTED_NO_CONSTRR +#define Q23_TL_EXPECTED_NO_CONSTRR // GCC < 5 doesn't support some standard C++11 type traits -#define TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ +#define Q23_TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ std::has_trivial_copy_constructor<T> -#define TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \ +#define Q23_TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \ std::has_trivial_copy_assign<T> // This one will be different for GCC 5.7 if it's ever supported -#define TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T) \ +#define Q23_TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T) \ std::is_trivially_destructible<T> // GCC 5 < v < 8 has a bug in is_trivially_copy_constructible which breaks // std::vector for non-copyable types #elif (defined(__GNUC__) && __GNUC__ < 8 && !defined(__clang__)) -#ifndef TL_GCC_LESS_8_TRIVIALLY_COPY_CONSTRUCTIBLE_MUTEX -#define TL_GCC_LESS_8_TRIVIALLY_COPY_CONSTRUCTIBLE_MUTEX +#ifndef Q23_TL_GCC_LESS_8_TRIVIALLY_COPY_CONSTRUCTIBLE_MUTEX +#define Q23_TL_GCC_LESS_8_TRIVIALLY_COPY_CONSTRUCTIBLE_MUTEX QT_BEGIN_NAMESPACE namespace q23 { namespace detail { @@ -121,50 +121,50 @@ struct is_trivially_copy_constructible<std::vector<T, A>> : std::false_type {}; QT_END_NAMESPACE #endif -#define TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ +#define Q23_TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ q23::detail::is_trivially_copy_constructible<T> -#define TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \ +#define Q23_TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \ std::is_trivially_copy_assignable<T> -#define TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T) \ +#define Q23_TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T) \ std::is_trivially_destructible<T> #else -#define TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ +#define Q23_TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ std::is_trivially_copy_constructible<T> -#define TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \ +#define Q23_TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \ std::is_trivially_copy_assignable<T> -#define TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T) \ +#define Q23_TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T) \ std::is_trivially_destructible<T> #endif #ifdef _MSVC_LANG -#define TL_CPLUSPLUS _MSVC_LANG +#define Q23_TL_CPLUSPLUS _MSVC_LANG #else -#define TL_CPLUSPLUS __cplusplus +#define Q23_TL_CPLUSPLUS __cplusplus #endif -#if TL_CPLUSPLUS > 201103L -#define TL_EXPECTED_CXX14 +#if Q23_TL_CPLUSPLUS > 201103L +#define Q23_TL_EXPECTED_CXX14 #endif -#ifdef TL_EXPECTED_GCC49 -#define TL_EXPECTED_GCC49_CONSTEXPR +#ifdef Q23_TL_EXPECTED_GCC49 +#define Q23_TL_EXPECTED_GCC49_CONSTEXPR #else -#define TL_EXPECTED_GCC49_CONSTEXPR constexpr +#define Q23_TL_EXPECTED_GCC49_CONSTEXPR constexpr #endif -#if (TL_CPLUSPLUS == 201103L || defined(TL_EXPECTED_MSVC2015) || \ - defined(TL_EXPECTED_GCC49)) -#define TL_EXPECTED_11_CONSTEXPR +#if (Q23_TL_CPLUSPLUS == 201103L || defined(Q23_TL_EXPECTED_MSVC2015) || \ + defined(Q23_TL_EXPECTED_GCC49)) +#define Q23_TL_EXPECTED_11_CONSTEXPR #else -#define TL_EXPECTED_11_CONSTEXPR constexpr +#define Q23_TL_EXPECTED_11_CONSTEXPR constexpr #endif QT_BEGIN_NAMESPACE namespace q23 { template <class T, class E> class expected; -#ifndef TL_MONOSTATE_INPLACE_MUTEX -#define TL_MONOSTATE_INPLACE_MUTEX +#ifndef Q23_TL_MONOSTATE_INPLACE_MUTEX +#define Q23_TL_MONOSTATE_INPLACE_MUTEX class monostate {}; struct in_place_t { @@ -196,8 +196,8 @@ public: : m_val(l, std::forward<Args>(args)...) {} constexpr const E &error() const & { return m_val; } - TL_EXPECTED_11_CONSTEXPR E &error() & { return m_val; } - TL_EXPECTED_11_CONSTEXPR E &&error() && { return std::move(m_val); } + Q23_TL_EXPECTED_11_CONSTEXPR E &error() & { return m_val; } + Q23_TL_EXPECTED_11_CONSTEXPR E &&error() && { return std::move(m_val); } constexpr const E &&error() const && { return std::move(m_val); } private: @@ -245,8 +245,8 @@ static constexpr unexpect_t unexpect{}; namespace detail { template <typename E> -[[noreturn]] TL_EXPECTED_11_CONSTEXPR void throw_exception(E &&e) { -#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED +[[noreturn]] Q23_TL_EXPECTED_11_CONSTEXPR void throw_exception(E &&e) { +#ifdef Q23_TL_EXPECTED_EXCEPTIONS_ENABLED throw std::forward<E>(e); #else (void)e; @@ -258,8 +258,8 @@ template <typename E> #endif } -#ifndef TL_TRAITS_MUTEX -#define TL_TRAITS_MUTEX +#ifndef Q23_TL_TRAITS_MUTEX +#define Q23_TL_TRAITS_MUTEX // C++14-style aliases for brevity template <class T> using remove_const_t = typename std::remove_const<T>::type; template <class T> @@ -278,13 +278,13 @@ struct conjunction<B, Bs...> : std::conditional<bool(B::value), conjunction<Bs...>, B>::type {}; #if defined(_LIBCPP_VERSION) && __cplusplus == 201103L -#define TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND +#define Q23_TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND #endif // In C++11 mode, there's an issue in libc++'s std::mem_fn // which results in a hard-error when using it in a noexcept expression // in some cases. This is a check to workaround the common failing case. -#ifdef TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND +#ifdef Q23_TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND template <class T> struct is_pointer_to_non_const_member_func : std::false_type {}; template <class T, class Ret, class... Args> @@ -315,7 +315,7 @@ template <class T> struct is_const_or_const_ref<T const> : std::true_type {}; // https://stackoverflow.com/questions/38288042/c11-14-invoke-workaround template < typename Fn, typename... Args, -#ifdef TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND +#ifdef Q23_TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND typename = enable_if_t<!(is_pointer_to_non_const_member_func<Fn>::value && is_const_or_const_ref<Args...>::value)>, #endif @@ -574,7 +574,7 @@ template <class T, class E> struct expected_storage_base<T, E, true, true> { // T is trivial, E is not. template <class T, class E> struct expected_storage_base<T, E, true, false> { constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {} - TL_EXPECTED_MSVC2015_CONSTEXPR expected_storage_base(no_init_t) + Q23_TL_EXPECTED_MSVC2015_CONSTEXPR expected_storage_base(no_init_t) : m_no_init(), m_has_val(false) {} template <class... Args, @@ -675,7 +675,7 @@ template <class E> struct expected_storage_base<void, E, false, true> { #if __GNUC__ <= 5 //no constexpr for GCC 4/5 bug #else - TL_EXPECTED_MSVC2015_CONSTEXPR + Q23_TL_EXPECTED_MSVC2015_CONSTEXPR #endif expected_storage_base() : m_has_val(true) {} @@ -770,7 +770,7 @@ struct expected_operations_base : expected_storage_base<T, E> { this->m_has_val = false; } -#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED +#ifdef Q23_TL_EXPECTED_EXCEPTIONS_ENABLED // These assign overloads ensure that the most efficient assignment // implementation is used while maintaining the strong exception guarantee. @@ -820,7 +820,7 @@ struct expected_operations_base : expected_storage_base<T, E> { auto tmp = std::move(geterr()); geterr().~unexpected<E>(); -#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED +#ifdef Q23_TL_EXPECTED_EXCEPTIONS_ENABLED try { construct(rhs.get()); } catch (...) { @@ -855,7 +855,7 @@ struct expected_operations_base : expected_storage_base<T, E> { if (!this->m_has_val && rhs.m_has_val) { auto tmp = std::move(geterr()); geterr().~unexpected<E>(); -#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED +#ifdef Q23_TL_EXPECTED_EXCEPTIONS_ENABLED try { construct(std::move(rhs).get()); } catch (...) { @@ -911,27 +911,27 @@ struct expected_operations_base : expected_storage_base<T, E> { bool has_value() const { return this->m_has_val; } - TL_EXPECTED_11_CONSTEXPR T &get() & { return this->m_val; } + Q23_TL_EXPECTED_11_CONSTEXPR T &get() & { return this->m_val; } constexpr const T &get() const & { return this->m_val; } - TL_EXPECTED_11_CONSTEXPR T &&get() && { return std::move(this->m_val); } -#ifndef TL_EXPECTED_NO_CONSTRR + Q23_TL_EXPECTED_11_CONSTEXPR T &&get() && { return std::move(this->m_val); } +#ifndef Q23_TL_EXPECTED_NO_CONSTRR constexpr const T &&get() const && { return std::move(this->m_val); } #endif - TL_EXPECTED_11_CONSTEXPR unexpected<E> &geterr() & { + Q23_TL_EXPECTED_11_CONSTEXPR unexpected<E> &geterr() & { return this->m_unexpect; } constexpr const unexpected<E> &geterr() const & { return this->m_unexpect; } - TL_EXPECTED_11_CONSTEXPR unexpected<E> &&geterr() && { + Q23_TL_EXPECTED_11_CONSTEXPR unexpected<E> &&geterr() && { return std::move(this->m_unexpect); } -#ifndef TL_EXPECTED_NO_CONSTRR +#ifndef Q23_TL_EXPECTED_NO_CONSTRR constexpr const unexpected<E> &&geterr() const && { return std::move(this->m_unexpect); } #endif - TL_EXPECTED_11_CONSTEXPR void destroy_val() { get().~T(); } + Q23_TL_EXPECTED_11_CONSTEXPR void destroy_val() { get().~T(); } }; // This base class provides some handy member functions which can be used in @@ -971,20 +971,20 @@ struct expected_operations_base<void, E> : expected_storage_base<void, E> { bool has_value() const { return this->m_has_val; } - TL_EXPECTED_11_CONSTEXPR unexpected<E> &geterr() & { + Q23_TL_EXPECTED_11_CONSTEXPR unexpected<E> &geterr() & { return this->m_unexpect; } constexpr const unexpected<E> &geterr() const & { return this->m_unexpect; } - TL_EXPECTED_11_CONSTEXPR unexpected<E> &&geterr() && { + Q23_TL_EXPECTED_11_CONSTEXPR unexpected<E> &&geterr() && { return std::move(this->m_unexpect); } -#ifndef TL_EXPECTED_NO_CONSTRR +#ifndef Q23_TL_EXPECTED_NO_CONSTRR constexpr const unexpected<E> &&geterr() const && { return std::move(this->m_unexpect); } #endif - TL_EXPECTED_11_CONSTEXPR void destroy_val() { + Q23_TL_EXPECTED_11_CONSTEXPR void destroy_val() { // no-op } }; @@ -992,8 +992,8 @@ struct expected_operations_base<void, E> : expected_storage_base<void, E> { // This class manages conditionally having a trivial copy constructor // This specialization is for when T and E are trivially copy constructible template <class T, class E, - bool = is_void_or<T, TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T)>:: - value &&TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(E)::value, + bool = is_void_or<T, Q23_TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T)>:: + value &&Q23_TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(E)::value, bool = (is_copy_constructible_or_void<T>::value && std::is_copy_constructible<E>::value)> struct expected_copy_base : expected_operations_base<T, E> { @@ -1025,7 +1025,7 @@ struct expected_copy_base<T, E, false, true> : expected_operations_base<T, E> { // doesn't implement an analogue to std::is_trivially_move_constructible. We // have to make do with a non-trivial move constructor even if T is trivially // move constructible -#ifndef TL_EXPECTED_GCC49 +#ifndef Q23_TL_EXPECTED_GCC49 template <class T, class E, bool = is_void_or<T, std::is_trivially_move_constructible<T>>::value &&std::is_trivially_move_constructible<E>::value> @@ -1058,12 +1058,12 @@ struct expected_move_base<T, E, false> : expected_copy_base<T, E> { // This class manages conditionally having a trivial copy assignment operator template <class T, class E, bool = is_void_or< - T, conjunction<TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T), - TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T), - TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T)>>::value - &&TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(E)::value - &&TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(E)::value - &&TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(E)::value, + T, conjunction<Q23_TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T), + Q23_TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T), + Q23_TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T)>>::value + &&Q23_TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(E)::value + &&Q23_TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(E)::value + &&Q23_TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(E)::value, bool = (is_copy_constructible_or_void<T>::value && std::is_copy_constructible<E>::value && is_copy_assignable_or_void<T>::value && @@ -1093,7 +1093,7 @@ struct expected_copy_assign_base<T, E, false, true> : expected_move_base<T, E> { // doesn't implement an analogue to std::is_trivially_move_assignable. We have // to make do with a non-trivial move assignment operator even if T is trivially // move assignable -#ifndef TL_EXPECTED_GCC49 +#ifndef Q23_TL_EXPECTED_GCC49 template <class T, class E, bool = is_void_or<T, conjunction<std::is_trivially_destructible<T>, @@ -1330,10 +1330,10 @@ class expected : private detail::expected_move_assign_base<T, E>, template <class U = T, detail::enable_if_t<!std::is_void<U>::value> * = nullptr> - TL_EXPECTED_11_CONSTEXPR U &val() { + Q23_TL_EXPECTED_11_CONSTEXPR U &val() { return this->m_val; } - TL_EXPECTED_11_CONSTEXPR unexpected<E> &err() { return this->m_unexpect; } + Q23_TL_EXPECTED_11_CONSTEXPR unexpected<E> &err() { return this->m_unexpect; } template <class U = T, detail::enable_if_t<!std::is_void<U>::value> * = nullptr> @@ -1350,19 +1350,19 @@ public: typedef E error_type; typedef unexpected<E> unexpected_type; -#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ - !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) - template <class F> TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) & { +#if defined(Q23_TL_EXPECTED_CXX14) && !defined(Q23_TL_EXPECTED_GCC49) && \ + !defined(Q23_TL_EXPECTED_GCC54) && !defined(Q23_TL_EXPECTED_GCC55) + template <class F> Q23_TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) & { return and_then_impl(*this, std::forward<F>(f)); } - template <class F> TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) && { + template <class F> Q23_TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) && { return and_then_impl(std::move(*this), std::forward<F>(f)); } template <class F> constexpr auto and_then(F &&f) const & { return and_then_impl(*this, std::forward<F>(f)); } -#ifndef TL_EXPECTED_NO_CONSTRR +#ifndef Q23_TL_EXPECTED_NO_CONSTRR template <class F> constexpr auto and_then(F &&f) const && { return and_then_impl(std::move(*this), std::forward<F>(f)); } @@ -1370,13 +1370,13 @@ public: #else template <class F> - TL_EXPECTED_11_CONSTEXPR auto + Q23_TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) & -> decltype(and_then_impl(std::declval<expected &>(), std::forward<F>(f))) { return and_then_impl(*this, std::forward<F>(f)); } template <class F> - TL_EXPECTED_11_CONSTEXPR auto + Q23_TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) && -> decltype(and_then_impl(std::declval<expected &&>(), std::forward<F>(f))) { return and_then_impl(std::move(*this), std::forward<F>(f)); @@ -1387,7 +1387,7 @@ public: return and_then_impl(*this, std::forward<F>(f)); } -#ifndef TL_EXPECTED_NO_CONSTRR +#ifndef Q23_TL_EXPECTED_NO_CONSTRR template <class F> constexpr auto and_then(F &&f) const && -> decltype(and_then_impl( std::declval<expected const &&>(), std::forward<F>(f))) { @@ -1396,12 +1396,12 @@ public: #endif #endif -#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ - !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) - template <class F> TL_EXPECTED_11_CONSTEXPR auto map(F &&f) & { +#if defined(Q23_TL_EXPECTED_CXX14) && !defined(Q23_TL_EXPECTED_GCC49) && \ + !defined(Q23_TL_EXPECTED_GCC54) && !defined(Q23_TL_EXPECTED_GCC55) + template <class F> Q23_TL_EXPECTED_11_CONSTEXPR auto map(F &&f) & { return expected_map_impl(*this, std::forward<F>(f)); } - template <class F> TL_EXPECTED_11_CONSTEXPR auto map(F &&f) && { + template <class F> Q23_TL_EXPECTED_11_CONSTEXPR auto map(F &&f) && { return expected_map_impl(std::move(*this), std::forward<F>(f)); } template <class F> constexpr auto map(F &&f) const & { @@ -1412,13 +1412,13 @@ public: } #else template <class F> - TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl( + Q23_TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl( std::declval<expected &>(), std::declval<F &&>())) map(F &&f) & { return expected_map_impl(*this, std::forward<F>(f)); } template <class F> - TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl(std::declval<expected>(), + Q23_TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl(std::declval<expected>(), std::declval<F &&>())) map(F &&f) && { return expected_map_impl(std::move(*this), std::forward<F>(f)); @@ -1430,7 +1430,7 @@ public: return expected_map_impl(*this, std::forward<F>(f)); } -#ifndef TL_EXPECTED_NO_CONSTRR +#ifndef Q23_TL_EXPECTED_NO_CONSTRR template <class F> constexpr decltype(expected_map_impl(std::declval<const expected &&>(), std::declval<F &&>())) @@ -1440,12 +1440,12 @@ public: #endif #endif -#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ - !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) - template <class F> TL_EXPECTED_11_CONSTEXPR auto transform(F &&f) & { +#if defined(Q23_TL_EXPECTED_CXX14) && !defined(Q23_TL_EXPECTED_GCC49) && \ + !defined(Q23_TL_EXPECTED_GCC54) && !defined(Q23_TL_EXPECTED_GCC55) + template <class F> Q23_TL_EXPECTED_11_CONSTEXPR auto transform(F &&f) & { return expected_map_impl(*this, std::forward<F>(f)); } - template <class F> TL_EXPECTED_11_CONSTEXPR auto transform(F &&f) && { + template <class F> Q23_TL_EXPECTED_11_CONSTEXPR auto transform(F &&f) && { return expected_map_impl(std::move(*this), std::forward<F>(f)); } template <class F> constexpr auto transform(F &&f) const & { @@ -1456,13 +1456,13 @@ public: } #else template <class F> - TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl( + Q23_TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl( std::declval<expected &>(), std::declval<F &&>())) transform(F &&f) & { return expected_map_impl(*this, std::forward<F>(f)); } template <class F> - TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl(std::declval<expected>(), + Q23_TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl(std::declval<expected>(), std::declval<F &&>())) transform(F &&f) && { return expected_map_impl(std::move(*this), std::forward<F>(f)); @@ -1474,7 +1474,7 @@ public: return expected_map_impl(*this, std::forward<F>(f)); } -#ifndef TL_EXPECTED_NO_CONSTRR +#ifndef Q23_TL_EXPECTED_NO_CONSTRR template <class F> constexpr decltype(expected_map_impl(std::declval<const expected &&>(), std::declval<F &&>())) @@ -1484,12 +1484,12 @@ public: #endif #endif -#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ - !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) - template <class F> TL_EXPECTED_11_CONSTEXPR auto map_error(F &&f) & { +#if defined(Q23_TL_EXPECTED_CXX14) && !defined(Q23_TL_EXPECTED_GCC49) && \ + !defined(Q23_TL_EXPECTED_GCC54) && !defined(Q23_TL_EXPECTED_GCC55) + template <class F> Q23_TL_EXPECTED_11_CONSTEXPR auto map_error(F &&f) & { return map_error_impl(*this, std::forward<F>(f)); } - template <class F> TL_EXPECTED_11_CONSTEXPR auto map_error(F &&f) && { + template <class F> Q23_TL_EXPECTED_11_CONSTEXPR auto map_error(F &&f) && { return map_error_impl(std::move(*this), std::forward<F>(f)); } template <class F> constexpr auto map_error(F &&f) const & { @@ -1500,13 +1500,13 @@ public: } #else template <class F> - TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval<expected &>(), + Q23_TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval<expected &>(), std::declval<F &&>())) map_error(F &&f) & { return map_error_impl(*this, std::forward<F>(f)); } template <class F> - TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval<expected &&>(), + Q23_TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval<expected &&>(), std::declval<F &&>())) map_error(F &&f) && { return map_error_impl(std::move(*this), std::forward<F>(f)); @@ -1518,7 +1518,7 @@ public: return map_error_impl(*this, std::forward<F>(f)); } -#ifndef TL_EXPECTED_NO_CONSTRR +#ifndef Q23_TL_EXPECTED_NO_CONSTRR template <class F> constexpr decltype(map_error_impl(std::declval<const expected &&>(), std::declval<F &&>())) @@ -1527,12 +1527,12 @@ public: } #endif #endif -#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ - !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) - template <class F> TL_EXPECTED_11_CONSTEXPR auto transform_error(F &&f) & { +#if defined(Q23_TL_EXPECTED_CXX14) && !defined(Q23_TL_EXPECTED_GCC49) && \ + !defined(Q23_TL_EXPECTED_GCC54) && !defined(Q23_TL_EXPECTED_GCC55) + template <class F> Q23_TL_EXPECTED_11_CONSTEXPR auto transform_error(F &&f) & { return map_error_impl(*this, std::forward<F>(f)); } - template <class F> TL_EXPECTED_11_CONSTEXPR auto transform_error(F &&f) && { + template <class F> Q23_TL_EXPECTED_11_CONSTEXPR auto transform_error(F &&f) && { return map_error_impl(std::move(*this), std::forward<F>(f)); } template <class F> constexpr auto transform_error(F &&f) const & { @@ -1543,13 +1543,13 @@ public: } #else template <class F> - TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval<expected &>(), + Q23_TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval<expected &>(), std::declval<F &&>())) transform_error(F &&f) & { return map_error_impl(*this, std::forward<F>(f)); } template <class F> - TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval<expected &&>(), + Q23_TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval<expected &&>(), std::declval<F &&>())) transform_error(F &&f) && { return map_error_impl(std::move(*this), std::forward<F>(f)); @@ -1561,7 +1561,7 @@ public: return map_error_impl(*this, std::forward<F>(f)); } -#ifndef TL_EXPECTED_NO_CONSTRR +#ifndef Q23_TL_EXPECTED_NO_CONSTRR template <class F> constexpr decltype(map_error_impl(std::declval<const expected &&>(), std::declval<F &&>())) @@ -1570,11 +1570,11 @@ public: } #endif #endif - template <class F> expected TL_EXPECTED_11_CONSTEXPR or_else(F &&f) & { + template <class F> expected Q23_TL_EXPECTED_11_CONSTEXPR or_else(F &&f) & { return or_else_impl(*this, std::forward<F>(f)); } - template <class F> expected TL_EXPECTED_11_CONSTEXPR or_else(F &&f) && { + template <class F> expected Q23_TL_EXPECTED_11_CONSTEXPR or_else(F &&f) && { return or_else_impl(std::move(*this), std::forward<F>(f)); } @@ -1582,7 +1582,7 @@ public: return or_else_impl(*this, std::forward<F>(f)); } -#ifndef TL_EXPECTED_NO_CONSTRR +#ifndef Q23_TL_EXPECTED_NO_CONSTRR template <class F> expected constexpr or_else(F &&f) const && { return or_else_impl(std::move(*this), std::forward<F>(f)); } @@ -1664,7 +1664,7 @@ public: nullptr, detail::expected_enable_from_other<T, E, U, G, const U &, const G &> * = nullptr> - explicit TL_EXPECTED_11_CONSTEXPR expected(const expected<U, G> &rhs) + explicit Q23_TL_EXPECTED_11_CONSTEXPR expected(const expected<U, G> &rhs) : ctor_base(detail::default_constructor_tag{}) { if (rhs.has_value()) { this->construct(*rhs); @@ -1679,7 +1679,7 @@ public: nullptr, detail::expected_enable_from_other<T, E, U, G, const U &, const G &> * = nullptr> - TL_EXPECTED_11_CONSTEXPR expected(const expected<U, G> &rhs) + Q23_TL_EXPECTED_11_CONSTEXPR expected(const expected<U, G> &rhs) : ctor_base(detail::default_constructor_tag{}) { if (rhs.has_value()) { this->construct(*rhs); @@ -1693,7 +1693,7 @@ public: detail::enable_if_t<!(std::is_convertible<U &&, T>::value && std::is_convertible<G &&, E>::value)> * = nullptr, detail::expected_enable_from_other<T, E, U, G, U &&, G &&> * = nullptr> - explicit TL_EXPECTED_11_CONSTEXPR expected(expected<U, G> &&rhs) + explicit Q23_TL_EXPECTED_11_CONSTEXPR expected(expected<U, G> &&rhs) : ctor_base(detail::default_constructor_tag{}) { if (rhs.has_value()) { this->construct(std::move(*rhs)); @@ -1707,7 +1707,7 @@ public: detail::enable_if_t<(std::is_convertible<U &&, T>::value && std::is_convertible<G &&, E>::value)> * = nullptr, detail::expected_enable_from_other<T, E, U, G, U &&, G &&> * = nullptr> - TL_EXPECTED_11_CONSTEXPR expected(expected<U, G> &&rhs) + Q23_TL_EXPECTED_11_CONSTEXPR expected(expected<U, G> &&rhs) : ctor_base(detail::default_constructor_tag{}) { if (rhs.has_value()) { this->construct(std::move(*rhs)); @@ -1720,14 +1720,14 @@ public: class U = T, detail::enable_if_t<!std::is_convertible<U &&, T>::value> * = nullptr, detail::expected_enable_forward_value<T, E, U> * = nullptr> - explicit TL_EXPECTED_MSVC2015_CONSTEXPR expected(U &&v) + explicit Q23_TL_EXPECTED_MSVC2015_CONSTEXPR expected(U &&v) : expected(in_place, std::forward<U>(v)) {} template < class U = T, detail::enable_if_t<std::is_convertible<U &&, T>::value> * = nullptr, detail::expected_enable_forward_value<T, E, U> * = nullptr> - TL_EXPECTED_MSVC2015_CONSTEXPR expected(U &&v) + Q23_TL_EXPECTED_MSVC2015_CONSTEXPR expected(U &&v) : expected(in_place, std::forward<U>(v)) {} template < @@ -1773,7 +1773,7 @@ public: auto tmp = std::move(err()); err().~unexpected<E>(); -#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED +#ifdef Q23_TL_EXPECTED_EXCEPTIONS_ENABLED try { ::new (valptr()) T(std::forward<U>(v)); this->m_has_val = true; @@ -1842,7 +1842,7 @@ public: auto tmp = std::move(err()); err().~unexpected<E>(); -#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED +#ifdef Q23_TL_EXPECTED_EXCEPTIONS_ENABLED try { ::new (valptr()) T(std::forward<Args>(args)...); this->m_has_val = true; @@ -1882,7 +1882,7 @@ public: auto tmp = std::move(err()); err().~unexpected<E>(); -#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED +#ifdef Q23_TL_EXPECTED_EXCEPTIONS_ENABLED try { ::new (valptr()) T(il, std::forward<Args>(args)...); this->m_has_val = true; @@ -1943,7 +1943,7 @@ private: move_constructing_e_can_throw) { auto temp = std::move(val()); val().~T(); -#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED +#ifdef Q23_TL_EXPECTED_EXCEPTIONS_ENABLED try { ::new (errptr()) unexpected_type(std::move(rhs.err())); rhs.err().~unexpected_type(); @@ -1966,7 +1966,7 @@ private: e_is_nothrow_move_constructible) { auto temp = std::move(rhs.err()); rhs.err().~unexpected_type(); -#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED +#ifdef Q23_TL_EXPECTED_EXCEPTIONS_ENABLED try { ::new (rhs.valptr()) T(std::move(val())); val().~T(); @@ -2008,36 +2008,36 @@ public: } constexpr const T *operator->() const { - TL_ASSERT(has_value()); + Q23_TL_ASSERT(has_value()); return valptr(); } - TL_EXPECTED_11_CONSTEXPR T *operator->() { - TL_ASSERT(has_value()); + Q23_TL_EXPECTED_11_CONSTEXPR T *operator->() { + Q23_TL_ASSERT(has_value()); return valptr(); } template <class U = T, detail::enable_if_t<!std::is_void<U>::value> * = nullptr> constexpr const U &operator*() const & { - TL_ASSERT(has_value()); + Q23_TL_ASSERT(has_value()); return val(); } template <class U = T, detail::enable_if_t<!std::is_void<U>::value> * = nullptr> - TL_EXPECTED_11_CONSTEXPR U &operator*() & { - TL_ASSERT(has_value()); + Q23_TL_EXPECTED_11_CONSTEXPR U &operator*() & { + Q23_TL_ASSERT(has_value()); return val(); } template <class U = T, detail::enable_if_t<!std::is_void<U>::value> * = nullptr> constexpr const U &&operator*() const && { - TL_ASSERT(has_value()); + Q23_TL_ASSERT(has_value()); return std::move(val()); } template <class U = T, detail::enable_if_t<!std::is_void<U>::value> * = nullptr> - TL_EXPECTED_11_CONSTEXPR U &&operator*() && { - TL_ASSERT(has_value()); + Q23_TL_EXPECTED_11_CONSTEXPR U &&operator*() && { + Q23_TL_ASSERT(has_value()); return std::move(val()); } @@ -2046,47 +2046,47 @@ public: template <class U = T, detail::enable_if_t<!std::is_void<U>::value> * = nullptr> - TL_EXPECTED_11_CONSTEXPR const U &value() const & { + Q23_TL_EXPECTED_11_CONSTEXPR const U &value() const & { if (!has_value()) detail::throw_exception(bad_expected_access<E>(err().error())); return val(); } template <class U = T, detail::enable_if_t<!std::is_void<U>::value> * = nullptr> - TL_EXPECTED_11_CONSTEXPR U &value() & { + Q23_TL_EXPECTED_11_CONSTEXPR U &value() & { if (!has_value()) detail::throw_exception(bad_expected_access<E>(err().error())); return val(); } template <class U = T, detail::enable_if_t<!std::is_void<U>::value> * = nullptr> - TL_EXPECTED_11_CONSTEXPR const U &&value() const && { + Q23_TL_EXPECTED_11_CONSTEXPR const U &&value() const && { if (!has_value()) detail::throw_exception(bad_expected_access<E>(std::move(err()).error())); return std::move(val()); } template <class U = T, detail::enable_if_t<!std::is_void<U>::value> * = nullptr> - TL_EXPECTED_11_CONSTEXPR U &&value() && { + Q23_TL_EXPECTED_11_CONSTEXPR U &&value() && { if (!has_value()) detail::throw_exception(bad_expected_access<E>(std::move(err()).error())); return std::move(val()); } constexpr const E &error() const & { - TL_ASSERT(!has_value()); + Q23_TL_ASSERT(!has_value()); return err().error(); } - TL_EXPECTED_11_CONSTEXPR E &error() & { - TL_ASSERT(!has_value()); + Q23_TL_EXPECTED_11_CONSTEXPR E &error() & { + Q23_TL_ASSERT(!has_value()); return err().error(); } constexpr const E &&error() const && { - TL_ASSERT(!has_value()); + Q23_TL_ASSERT(!has_value()); return std::move(err().error()); } - TL_EXPECTED_11_CONSTEXPR E &&error() && { - TL_ASSERT(!has_value()); + Q23_TL_EXPECTED_11_CONSTEXPR E &&error() && { + Q23_TL_ASSERT(!has_value()); return std::move(err().error()); } @@ -2096,7 +2096,7 @@ public: "T must be copy-constructible and convertible to from U&&"); return bool(*this) ? **this : static_cast<T>(std::forward<U>(v)); } - template <class U> TL_EXPECTED_11_CONSTEXPR T value_or(U &&v) && { + template <class U> Q23_TL_EXPECTED_11_CONSTEXPR T value_or(U &&v) && { static_assert(std::is_move_constructible<T>::value && std::is_convertible<U &&, T>::value, "T must be move-constructible and convertible to from U&&"); @@ -2109,7 +2109,7 @@ template <class Exp> using exp_t = typename detail::decay_t<Exp>::value_type; template <class Exp> using err_t = typename detail::decay_t<Exp>::error_type; template <class Exp, class Ret> using ret_t = expected<Ret, err_t<Exp>>; -#ifdef TL_EXPECTED_CXX14 +#ifdef Q23_TL_EXPECTED_CXX14 template <class Exp, class F, detail::enable_if_t<!std::is_void<exp_t<Exp>>::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval<F>(), @@ -2156,7 +2156,7 @@ constexpr auto and_then_impl(Exp &&exp, F &&f) -> Ret { } #endif -#ifdef TL_EXPECTED_CXX14 +#ifdef Q23_TL_EXPECTED_CXX14 template <class Exp, class F, detail::enable_if_t<!std::is_void<exp_t<Exp>>::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval<F>(), @@ -2266,8 +2266,8 @@ auto expected_map_impl(Exp &&exp, F &&f) -> expected<void, err_t<Exp>> { } #endif -#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ - !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) +#if defined(Q23_TL_EXPECTED_CXX14) && !defined(Q23_TL_EXPECTED_GCC49) && \ + !defined(Q23_TL_EXPECTED_GCC54) && !defined(Q23_TL_EXPECTED_GCC55) template <class Exp, class F, detail::enable_if_t<!std::is_void<exp_t<Exp>>::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval<F>(), @@ -2382,7 +2382,7 @@ auto map_error_impl(Exp &&exp, F &&f) -> expected<exp_t<Exp>, monostate> { } #endif -#ifdef TL_EXPECTED_CXX14 +#ifdef Q23_TL_EXPECTED_CXX14 template <class Exp, class F, class Ret = decltype(detail::invoke(std::declval<F>(), std::declval<Exp>().error())), diff --git a/src/corelib/global/qnamespace.h b/src/corelib/global/qnamespace.h index fb37e8df8e4..8332d1fd9bf 100644 --- a/src/corelib/global/qnamespace.h +++ b/src/corelib/global/qnamespace.h @@ -1533,6 +1533,8 @@ namespace Qt { WhatsThisPropertyRole = 31, // QRangeModel support for QML's required property var modelData RangeModelDataRole = 40, + // QRangeModelAdapter support for accessing entire multi-role objects + RangeModelAdapterRole = 41, // Reserved UserRole = 0x0100, diff --git a/src/corelib/global/qnamespace.qdoc b/src/corelib/global/qnamespace.qdoc index e530b28e7f1..69418af4b68 100644 --- a/src/corelib/global/qnamespace.qdoc +++ b/src/corelib/global/qnamespace.qdoc @@ -2830,6 +2830,7 @@ \omitvalue StatusTipPropertyRole \omitvalue WhatsThisPropertyRole \omitvalue RangeModelDataRole + \omitvalue RangeModelAdapterRole \omitvalue StandardItemFlagsRole \omitvalue FileInfoRole \omitvalue RemoteObjectsCacheRole diff --git a/src/corelib/global/qnumeric.h b/src/corelib/global/qnumeric.h index 48e736ff124..db32ae73556 100644 --- a/src/corelib/global/qnumeric.h +++ b/src/corelib/global/qnumeric.h @@ -627,6 +627,27 @@ QT_WARNING_DISABLE_FLOAT_COMPARE QT_WARNING_POP +namespace QtPrivate { +/* + A version of qFuzzyCompare that works for all values (qFuzzyCompare() + requires that neither argument is numerically 0). + + It's private because we need a fix for the many qFuzzyCompare() uses that + ignore the precondition, even for older branches. + + See QTBUG-142020 for discussion of a longer-term solution. +*/ +template <typename T, typename S> +[[nodiscard]] constexpr bool fuzzyCompare(const T &lhs, const S &rhs) noexcept +{ + static_assert(noexcept(qIsNull(lhs) && qIsNull(rhs) && qFuzzyIsNull(lhs - rhs) && qFuzzyCompare(lhs, rhs)), + "The operations qIsNull(), qFuzzyIsNull() and qFuzzyCompare() must be noexcept " + "for both argument types!"); + return qIsNull(lhs) || qIsNull(rhs) ? qFuzzyIsNull(lhs - rhs) : qFuzzyCompare(lhs, rhs); +} +} // namespace QtPrivate + + inline int qIntCast(double f) { return int(f); } inline int qIntCast(float f) { return int(f); } diff --git a/src/corelib/global/qoperatingsystemversion.cpp b/src/corelib/global/qoperatingsystemversion.cpp index d27a71526d8..32364fa8eb4 100644 --- a/src/corelib/global/qoperatingsystemversion.cpp +++ b/src/corelib/global/qoperatingsystemversion.cpp @@ -486,6 +486,12 @@ const QOperatingSystemVersionBase QOperatingSystemVersion::Windows11_22H2; */ /*! + \variable QOperatingSystemVersion::Windows11_25H2 + \brief a version corresponding to Windows 11 Version 25H2 (version 10.0.26200). + \since 6.11 + */ + +/*! \variable QOperatingSystemVersion::OSXMavericks \brief a version corresponding to OS X Mavericks (version 10.9). \since 5.9 diff --git a/src/corelib/global/qoperatingsystemversion.h b/src/corelib/global/qoperatingsystemversion.h index 75801a7ddcf..99866692f8c 100644 --- a/src/corelib/global/qoperatingsystemversion.h +++ b/src/corelib/global/qoperatingsystemversion.h @@ -148,6 +148,7 @@ public: static constexpr QOperatingSystemVersionBase Android14 { QOperatingSystemVersionBase::Android, 14, 0 }; static constexpr QOperatingSystemVersionBase Windows11_23H2 { QOperatingSystemVersionBase::Windows, 10, 0, 22631 }; static constexpr QOperatingSystemVersionBase Windows11_24H2 { QOperatingSystemVersionBase::Windows, 10, 0, 26100 }; + static constexpr QOperatingSystemVersionBase Windows11_25H2 { QOperatingSystemVersionBase::Windows, 10, 0, 26200 }; #if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) && !defined(QT_BOOTSTRAPPED) && !defined(Q_QDOC) }; diff --git a/src/corelib/global/qtdeprecationmarkers.h b/src/corelib/global/qtdeprecationmarkers.h index 6ebeb3afe89..6a6efe81b9f 100644 --- a/src/corelib/global/qtdeprecationmarkers.h +++ b/src/corelib/global/qtdeprecationmarkers.h @@ -258,6 +258,14 @@ QT_BEGIN_NAMESPACE # define QT_DEPRECATED_VERSION_6_14 #endif +#if QT_WARN_DEPRECATED_UP_TO >= QT_VERSION_CHECK(6, 15, 0) +# define QT_DEPRECATED_VERSION_X_6_15(text) QT_DEPRECATED_X(text) +# define QT_DEPRECATED_VERSION_6_15 QT_DEPRECATED +#else +# define QT_DEPRECATED_VERSION_X_6_15(text) +# define QT_DEPRECATED_VERSION_6_15 +#endif + #define QT_DEPRECATED_VERSION_X_5(minor, text) QT_DEPRECATED_VERSION_X_5_##minor(text) #define QT_DEPRECATED_VERSION_X(major, minor, text) QT_DEPRECATED_VERSION_X_##major##_##minor(text) diff --git a/src/corelib/global/qttranslation.qdoc b/src/corelib/global/qttranslation.qdoc index 191b3777db6..c8b3764614e 100644 --- a/src/corelib/global/qttranslation.qdoc +++ b/src/corelib/global/qttranslation.qdoc @@ -179,7 +179,7 @@ \fn QString qTrId(const char *id, int n = -1) \relates <QtTranslation> \reentrant - \since 6.9 + \since 6.11 \brief The qTrId function is an alias for qtTrId. diff --git a/src/corelib/io/qfsfileengine.cpp b/src/corelib/io/qfsfileengine.cpp index 0771c15584b..d3c398f0860 100644 --- a/src/corelib/io/qfsfileengine.cpp +++ b/src/corelib/io/qfsfileengine.cpp @@ -249,6 +249,12 @@ bool QFSFileEngine::open(QIODevice::OpenMode openMode, FILE *fh, QFile::FileHand } /*! + \class QFSFileEnginePrivate + \inmodule QtCore + \internal +*/ + +/*! Opens the file handle \a fh using the open mode \a flags. */ bool QFSFileEnginePrivate::openFh(QIODevice::OpenMode openMode, FILE *fh) diff --git a/src/corelib/io/qiooperation_p_p.h b/src/corelib/io/qiooperation_p_p.h index 470e0858fd3..be780d4c785 100644 --- a/src/corelib/io/qiooperation_p_p.h +++ b/src/corelib/io/qiooperation_p_p.h @@ -24,6 +24,10 @@ #include <QtCore/qspan.h> #include <QtCore/qvarlengtharray.h> +#ifdef QT_RANDOMACCESSASYNCFILE_QIORING +#include <QtCore/private/qioring_p.h> +#endif + #include <variant> QT_BEGIN_NAMESPACE diff --git a/src/corelib/io/qioring.cpp b/src/corelib/io/qioring.cpp new file mode 100644 index 00000000000..2eb013e24fc --- /dev/null +++ b/src/corelib/io/qioring.cpp @@ -0,0 +1,101 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default + +#include "qioring_p.h" + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcQIORing, "qt.core.ioring", QtCriticalMsg) + +QIORing *QIORing::sharedInstance() +{ + thread_local QIORing instance; + if (!instance.initializeIORing()) + return nullptr; + return &instance; +} + +QIORing::QIORing(quint32 submissionQueueSize, quint32 completionQueueSize) + : sqEntries(submissionQueueSize), cqEntries(completionQueueSize) +{ + // Destructor in respective _<platform>.cpp +} + +auto QIORing::queueRequestInternal(GenericRequestType &request) -> QueuedRequestStatus +{ + if (!ensureInitialized() || preparingRequests) { // preparingRequests protects against recursing + // inside callbacks of synchronous completions. + finishRequestWithError(request, QFileDevice::ResourceError); + addrItMap.remove(&request); + return QueuedRequestStatus::CompletedImmediately; + } + if (!lastUnqueuedIterator) + lastUnqueuedIterator.emplace(addrItMap[&request]); + + qCDebug(lcQIORing) << "Trying to submit request" << request.operation() + << "user data:" << std::addressof(request); + prepareRequests(); + // If this is now true we have, in some way, fulfilled the request: + const bool requestCompleted = !addrItMap.contains(&request); + const QueuedRequestStatus requestQueuedState = requestCompleted + ? QueuedRequestStatus::CompletedImmediately + : QueuedRequestStatus::Pending; + // We want to avoid notifying the kernel too often of tasks, so only do it if the queue is full, + // otherwise do it when we return to the event loop. + if (unstagedRequests == sqEntries && inFlightRequests <= cqEntries) { + submitRequests(); + return requestQueuedState; + } + if (stagePending || unstagedRequests == 0) + return requestQueuedState; + stagePending = true; + // We are not a QObject, but we always have the notifier, so use that for context: + QMetaObject::invokeMethod( + std::addressof(*notifier), [this] { submitRequests(); }, Qt::QueuedConnection); + return requestQueuedState; +} + +bool QIORing::waitForRequest(RequestHandle handle, QDeadlineTimer deadline) +{ + if (!handle || !addrItMap.contains(handle)) + return true; // : It was never there to begin with (so it is finished) + if (unstagedRequests) + submitRequests(); + completionReady(); // Try to process some pending completions + while (!deadline.hasExpired() && addrItMap.contains(handle)) { + if (!waitForCompletions(deadline)) + return false; + completionReady(); + } + return !addrItMap.contains(handle); +} + +namespace QtPrivate { +template <typename T> +using DetectResult = decltype(std::declval<const T &>().result); + +template <typename T> +constexpr bool HasResultMember = qxp::is_detected_v<DetectResult, T>; +} + +void QIORing::setFileErrorResult(QIORing::GenericRequestType &req, QFileDevice::FileError error) +{ + invokeOnOp(req, [error](auto *concreteRequest) { + if constexpr (QtPrivate::HasResultMember<decltype(*concreteRequest)>) + setFileErrorResult(*concreteRequest, error); + }); +} + +void QIORing::finishRequestWithError(QIORing::GenericRequestType &req, QFileDevice::FileError error) +{ + invokeOnOp(req, [error](auto *concreteRequest) { + if constexpr (QtPrivate::HasResultMember<decltype(*concreteRequest)>) + setFileErrorResult(*concreteRequest, error); + invokeCallback(*concreteRequest); + }); +} + +QT_END_NAMESPACE + +#include "moc_qioring_p.cpp" diff --git a/src/corelib/io/qioring_linux.cpp b/src/corelib/io/qioring_linux.cpp new file mode 100644 index 00000000000..2b5865f3c2d --- /dev/null +++ b/src/corelib/io/qioring_linux.cpp @@ -0,0 +1,730 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default + +#include "qioring_p.h" + +QT_REQUIRE_CONFIG(liburing); + +#include <QtCore/qobject.h> +#include <QtCore/qscopedvaluerollback.h> +#include <QtCore/private/qcore_unix_p.h> +#include <QtCore/private/qfiledevice_p.h> + +#include <liburing.h> +#include <sys/mman.h> +#include <sys/eventfd.h> +#include <sys/stat.h> + +QT_BEGIN_NAMESPACE +// From man write.2: +// On Linux, write() (and similar system calls) will transfer at most 0x7ffff000 (2,147,479,552) +// bytes, returning the number of bytes actually transferred. (This is true on both 32-bit and +// 64-bit systems.) +constexpr qsizetype MaxReadWriteLen = 0x7ffff000; // aka. MAX_RW_COUNT + +// We pretend that iovec and QSpans are the same, assert that size and alignment match: +static_assert(sizeof(iovec) + == sizeof(decltype(std::declval<QIORingRequest<QIORing::Operation::VectoredRead>>() + .destinations))); +static_assert(alignof(iovec) + == alignof(decltype(std::declval<QIORingRequest<QIORing::Operation::VectoredRead>>() + .destinations))); + +static io_uring_op toUringOp(QIORing::Operation op); +static void prepareFileReadWrite(io_uring_sqe *sqe, const QIORingRequestOffsetFdBase &request, + const void *address, qsizetype size); + +QIORing::~QIORing() +{ + if (eventDescriptor != -1) + close(eventDescriptor); + if (io_uringFd != -1) + close(io_uringFd); +} + +bool QIORing::initializeIORing() +{ + if (io_uringFd != -1) + return true; + + io_uring_params params{}; + params.flags = IORING_SETUP_CQSIZE; + params.cq_entries = cqEntries; + const int fd = io_uring_setup(sqEntries, ¶ms); + if (fd < 0) { + qErrnoWarning(-fd, "Failed to setup io_uring"); + return false; + } + io_uringFd = fd; + size_t submissionQueueSize = params.sq_off.array + (params.sq_entries * sizeof(quint32)); + size_t completionQueueSize = params.cq_off.cqes + (params.cq_entries * sizeof(io_uring_cqe)); + if (params.features & IORING_FEAT_SINGLE_MMAP) + submissionQueueSize = std::max(submissionQueueSize, completionQueueSize); + submissionQueue = mmap(nullptr, submissionQueueSize, PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_POPULATE, io_uringFd, IORING_OFF_SQ_RING); + if (submissionQueue == MAP_FAILED) { + qErrnoWarning(errno, "Failed to mmap io_uring submission queue"); + close(io_uringFd); + io_uringFd = -1; + return false; + } + const size_t submissionQueueEntriesSize = params.sq_entries * sizeof(io_uring_sqe); + submissionQueueEntries = static_cast<io_uring_sqe *>( + mmap(nullptr, submissionQueueEntriesSize, PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_POPULATE, io_uringFd, IORING_OFF_SQES)); + if (submissionQueueEntries == MAP_FAILED) { + qErrnoWarning(errno, "Failed to mmap io_uring submission queue entries"); + munmap(submissionQueue, submissionQueueSize); + close(io_uringFd); + io_uringFd = -1; + return false; + } + void *completionQueue = nullptr; + if (params.features & IORING_FEAT_SINGLE_MMAP) { + completionQueue = submissionQueue; + } else { + completionQueue = mmap(nullptr, completionQueueSize, PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_POPULATE, io_uringFd, IORING_OFF_CQ_RING); + if (completionQueue == MAP_FAILED) { + qErrnoWarning(errno, "Failed to mmap io_uring completion queue"); + munmap(submissionQueue, submissionQueueSize); + munmap(submissionQueueEntries, submissionQueueEntriesSize); + close(io_uringFd); + io_uringFd = -1; + return false; + } + } + sqEntries = params.sq_entries; + cqEntries = params.cq_entries; + + char *sq = static_cast<char *>(submissionQueue); + sqHead = reinterpret_cast<quint32 *>(sq + params.sq_off.head); + sqTail = reinterpret_cast<quint32 *>(sq + params.sq_off.tail); + sqIndexMask = reinterpret_cast<quint32 *>(sq + params.sq_off.ring_mask); + sqIndexArray = reinterpret_cast<quint32 *>(sq + params.sq_off.array); + + char *cq = static_cast<char *>(completionQueue); + cqHead = reinterpret_cast<quint32 *>(cq + params.cq_off.head); + cqTail = reinterpret_cast<quint32 *>(cq + params.cq_off.tail); + cqIndexMask = reinterpret_cast<quint32 *>(cq + params.cq_off.ring_mask); + completionQueueEntries = reinterpret_cast<io_uring_cqe *>(cq + params.cq_off.cqes); + + eventDescriptor = eventfd(0, 0); + io_uring_register(io_uringFd, IORING_REGISTER_EVENTFD, &eventDescriptor, 1); + + notifier.emplace(eventDescriptor, QSocketNotifier::Read); + QObject::connect(std::addressof(*notifier), &QSocketNotifier::activated, + std::addressof(*notifier), [this]() { completionReady(); }); + return true; +} + +template <QIORing::Operation Op> +Q_ALWAYS_INLINE QIORing::ReadWriteStatus QIORing::handleReadCompletion(const io_uring_cqe *cqe, GenericRequestType *request) +{ + auto *readRequest = request->requestData<Op>(); + Q_ASSERT(readRequest); + auto *destinations = [&readRequest]() { + if constexpr (Op == Operation::Read) + return &readRequest->destination; + else + return &readRequest->destinations[0]; + }(); + + if (cqe->res < 0) { + if (-cqe->res == ECANCELED) + readRequest->result.template emplace<QFileDevice::FileError>(QFileDevice::AbortError); + else + readRequest->result.template emplace<QFileDevice::FileError>(QFileDevice::ReadError); + } else if (auto *extra = request->getExtra<QtPrivate::ReadWriteExtra>()) { + const qint32 bytesRead = cqe->res; + qCDebug(lcQIORing) << "Partial read of" << bytesRead << "bytes completed"; + auto &readResult = [&readRequest]() -> QIORingResult<Op> & { + if (auto *result = std::get_if<QIORingResult<Op>>(&readRequest->result)) + return *result; + return readRequest->result.template emplace<QIORingResult<Op>>(); + }(); + readResult.bytesRead += bytesRead; + extra->spanOffset += qsizetype(bytesRead); + qCDebug(lcQIORing) << "Read operation progress: span" << extra->spanIndex << "offset" + << extra->spanOffset << "of" << destinations[extra->spanIndex].size() + << "bytes. Total read:" << readResult.bytesRead << "bytes"; + // The while loop is in case there is an empty span, we skip over it: + while (extra->spanOffset == destinations[extra->spanIndex].size()) { + // Move to next span + if (++extra->spanIndex == extra->numSpans) { + --ongoingSplitOperations; + return ReadWriteStatus::Finished; + } + extra->spanOffset = 0; + } + + QSpan<std::byte> span = destinations[extra->spanIndex].subspan(extra->spanOffset); + if (span.size() > MaxReadWriteLen) + span = span.first(MaxReadWriteLen); + + // Move the request such that it is next in the list to be processed: + auto &it = addrItMap[request]; + const auto where = lastUnqueuedIterator.value_or(pendingRequests.end()); + pendingRequests.splice(where, pendingRequests, it); + it = std::prev(where); + lastUnqueuedIterator = it; + + return ReadWriteStatus::MoreToDo; + } else { + auto &result = readRequest->result.template emplace<QIORingResult<Op>>(); + result.bytesRead = cqe->res; + } + return ReadWriteStatus::Finished; +} + +template <QIORing::Operation Op> +Q_ALWAYS_INLINE QIORing::ReadWriteStatus QIORing::handleWriteCompletion(const io_uring_cqe *cqe, GenericRequestType *request) +{ + auto *writeRequest = request->requestData<Op>(); + Q_ASSERT(writeRequest); + auto *sources = [&writeRequest]() { + if constexpr (Op == Operation::Write) + return &writeRequest->source; + else + return &writeRequest->sources[0]; + }(); + + if (cqe->res < 0) { + if (-cqe->res == ECANCELED) + writeRequest->result.template emplace<QFileDevice::FileError>(QFileDevice::AbortError); + else + writeRequest->result.template emplace<QFileDevice::FileError>(QFileDevice::WriteError); + } else if (auto *extra = request->getExtra<QtPrivate::ReadWriteExtra>()) { + const qint32 bytesWritten = cqe->res; + qCDebug(lcQIORing) << "Partial write of" << bytesWritten << "bytes completed"; + auto &writeResult = [&writeRequest]() -> QIORingResult<Op> & { + if (auto *result = std::get_if<QIORingResult<Op>>(&writeRequest->result)) + return *result; + return writeRequest->result.template emplace<QIORingResult<Op>>(); + }(); + writeResult.bytesWritten += bytesWritten; + extra->spanOffset += qsizetype(bytesWritten); + qCDebug(lcQIORing) << "Write operation progress: span" << extra->spanIndex << "offset" + << extra->spanOffset << "of" << sources[extra->spanIndex].size() + << "bytes. Total written:" << writeResult.bytesWritten << "bytes"; + // The while loop is in case there is an empty span, we skip over it: + while (extra->spanOffset == sources[extra->spanIndex].size()) { + // Move to next span + if (++extra->spanIndex == extra->numSpans) { + --ongoingSplitOperations; + return ReadWriteStatus::Finished; + } + extra->spanOffset = 0; + } + + QSpan<const std::byte> span = sources[extra->spanIndex].subspan(extra->spanOffset); + if (span.size() > MaxReadWriteLen) + span = span.first(MaxReadWriteLen); + + // Move the request such that it is next in the list to be processed: + auto &it = addrItMap[request]; + const auto where = lastUnqueuedIterator.value_or(pendingRequests.end()); + pendingRequests.splice(where, pendingRequests, it); + it = std::prev(where); + lastUnqueuedIterator = it; + + return ReadWriteStatus::MoreToDo; + } else { + auto &result = writeRequest->result.template emplace<QIORingResult<Op>>(); + result.bytesWritten = cqe->res; + } + return ReadWriteStatus::Finished; +} + +void QIORing::completionReady() +{ + // Drain the eventfd: + [[maybe_unused]] + quint64 ignored = 0; + std::ignore = read(eventDescriptor, &ignored, sizeof(ignored)); + + quint32 head = __atomic_load_n(cqHead, __ATOMIC_RELAXED); + const quint32 tail = __atomic_load_n(cqTail, __ATOMIC_ACQUIRE); + if (tail == head) + return; + + qCDebug(lcQIORing, + "Status of completion queue, total entries: %u, tail: %u, head: %u, to process: %u", + cqEntries, tail, head, (tail - head)); + while (head != tail) { + /* Get the entry */ + const io_uring_cqe *cqe = &completionQueueEntries[head & *cqIndexMask]; + ++head; + GenericRequestType *request = reinterpret_cast<GenericRequestType *>(cqe->user_data); + qCDebug(lcQIORing) << "Got completed entry. Operation:" << request->operation() + << "- user_data pointer:" << request; + switch (request->operation()) { + case Operation::Open: { + QIORingRequest<Operation::Open> + openRequest = request->template takeRequestData<Operation::Open>(); + if (cqe->res < 0) { + // qErrnoWarning(-cqe->res, "Failed to open"); + if (-cqe->res == ECANCELED) + openRequest.result.template emplace<QFileDevice::FileError>( + QFileDevice::AbortError); + else + openRequest.result.template emplace<QFileDevice::FileError>( + QFileDevice::OpenError); + } else { + auto &result = openRequest.result + .template emplace<QIORingResult<Operation::Open>>(); + result.fd = cqe->res; + } + invokeCallback(openRequest); + break; + } + case Operation::Close: { + QIORingRequest<Operation::Close> + closeRequest = request->template takeRequestData<Operation::Close>(); + if (cqe->res < 0) { + closeRequest.result.emplace<QFileDevice::FileError>(QFileDevice::OpenError); + } else { + closeRequest.result.emplace<QIORingResult<Operation::Close>>(); + } + invokeCallback(closeRequest); + break; + } + case Operation::Read: { + const ReadWriteStatus status = handleReadCompletion<Operation::Read>(cqe, request); + if (status == ReadWriteStatus::MoreToDo) + continue; + auto readRequest = request->takeRequestData<Operation::Read>(); + invokeCallback(readRequest); + break; + } + case Operation::Write: { + const ReadWriteStatus status = handleWriteCompletion<Operation::Write>(cqe, request); + if (status == ReadWriteStatus::MoreToDo) + continue; + auto writeRequest = request->takeRequestData<Operation::Write>(); + invokeCallback(writeRequest); + break; + } + case Operation::VectoredRead: { + const ReadWriteStatus status = handleReadCompletion<Operation::VectoredRead>(cqe, request); + if (status == ReadWriteStatus::MoreToDo) + continue; + auto readvRequest = request->takeRequestData<Operation::VectoredRead>(); + invokeCallback(readvRequest); + break; + } + case Operation::VectoredWrite: { + const ReadWriteStatus status = handleWriteCompletion<Operation::VectoredWrite>(cqe, request); + if (status == ReadWriteStatus::MoreToDo) + continue; + auto writevRequest = request->takeRequestData<Operation::VectoredWrite>(); + invokeCallback(writevRequest); + break; + } + case Operation::Flush: { + QIORingRequest<Operation::Flush> + flushRequest = request->template takeRequestData<Operation::Flush>(); + if (cqe->res < 0) { + flushRequest.result.emplace<QFileDevice::FileError>(QFileDevice::WriteError); + } else { + // No members to fill out, so just initialize to indicate success + flushRequest.result.emplace<QIORingResult<Operation::Flush>>(); + } + flushInProgress = false; + invokeCallback(flushRequest); + break; + } + case Operation::Cancel: { + QIORingRequest<Operation::Cancel> + cancelRequest = request->template takeRequestData<Operation::Cancel>(); + invokeCallback(cancelRequest); + break; + } + case Operation::Stat: { + QIORingRequest<Operation::Stat> + statRequest = request->template takeRequestData<Operation::Stat>(); + if (cqe->res < 0) { + statRequest.result.emplace<QFileDevice::FileError>(QFileDevice::OpenError); + } else { + struct statx *st = request->getExtra<struct statx>(); + Q_ASSERT(st); + auto &res = statRequest.result.emplace<QIORingResult<Operation::Stat>>(); + res.size = st->stx_size; + } + invokeCallback(statRequest); + break; + } + case Operation::NumOperations: + Q_UNREACHABLE_RETURN(); + break; + } + --inFlightRequests; + auto it = addrItMap.take(request); + pendingRequests.erase(it); + } + __atomic_store_n(cqHead, head, __ATOMIC_RELEASE); + qCDebug(lcQIORing, + "Done processing available completions, updated pointers, tail: %u, head: %u", tail, + head); + prepareRequests(); + if (!stagePending && unstagedRequests > 0) + submitRequests(); +} + +bool QIORing::waitForCompletions(QDeadlineTimer deadline) +{ + notifier->setEnabled(false); + auto reactivateNotifier = qScopeGuard([this]() { + notifier->setEnabled(true); + }); + + pollfd pfd = qt_make_pollfd(eventDescriptor, POLLIN); + return qt_safe_poll(&pfd, 1, deadline) > 0; +} + +bool QIORing::supportsOperation(Operation op) +{ + switch (op) { + case QtPrivate::Operation::Open: + case QtPrivate::Operation::Close: + case QtPrivate::Operation::Read: + case QtPrivate::Operation::Write: + case QtPrivate::Operation::VectoredRead: + case QtPrivate::Operation::VectoredWrite: + case QtPrivate::Operation::Flush: + case QtPrivate::Operation::Cancel: + case QtPrivate::Operation::Stat: + return true; + case QtPrivate::Operation::NumOperations: + return false; + } + return false; // May not always be unreachable! +} + +void QIORing::submitRequests() +{ + stagePending = false; + if (unstagedRequests == 0) + return; + + auto submitToRing = [this] { + int ret = io_uring_enter(io_uringFd, unstagedRequests, 0, 0, nullptr); + if (ret < 0) + qErrnoWarning("Error occurred notifying kernel about requests..."); + else + unstagedRequests -= ret; + qCDebug(lcQIORing) << "io_uring_enter returned" << ret; + return ret >= 0; + }; + if (submitToRing()) { + prepareRequests(); + if (unstagedRequests) + submitToRing(); + } +} + +namespace QtPrivate { +template <typename T> +using DetectFd = decltype(std::declval<const T &>().fd); + +template <typename T> +constexpr bool HasFdMember = qxp::is_detected_v<DetectFd, T>; +} // namespace QtPrivate + +bool QIORing::verifyFd(QIORing::GenericRequestType &req) +{ + bool result = true; + invokeOnOp(req, [&](auto *request) { + if constexpr (QtPrivate::HasFdMember<decltype(*request)>) { + result = request->fd > 0; + } + }); + return result; +} + +void QIORing::prepareRequests() +{ + if (!lastUnqueuedIterator) { + qCDebug(lcQIORing, "Nothing left to queue"); + return; + } + Q_ASSERT(!preparingRequests); + QScopedValueRollback<bool> prepareGuard(preparingRequests, true); + + quint32 tail = __atomic_load_n(sqTail, __ATOMIC_RELAXED); + const quint32 head = __atomic_load_n(sqHead, __ATOMIC_ACQUIRE); + qCDebug(lcQIORing, + "Status of submission queue, total entries: %u, tail: %u, head: %u, free: %u", + sqEntries, tail, head, sqEntries - (tail - head)); + + auto it = *lastUnqueuedIterator; + lastUnqueuedIterator.reset(); + const auto end = pendingRequests.end(); + bool anyQueued = false; + // Loop until we either: + // 1. Run out of requests to prepare for submission (it == end), + // 2. Have filled the submission queue (unstagedRequests == sqEntries) or, + // 3. The number of staged requests + currently processing/potentially finished requests is + // enough to fill the completion queue (inFlightRequests == cqEntries). + while (!flushInProgress && unstagedRequests != sqEntries && inFlightRequests != cqEntries + && it != end) { + const quint32 index = tail & *sqIndexMask; + io_uring_sqe *sqe = &submissionQueueEntries[index]; + *sqe = {}; + RequestPrepResult result = prepareRequest(sqe, *it); + + // QueueFull is unused on Linux: + Q_ASSERT(result != RequestPrepResult::QueueFull); + if (result == RequestPrepResult::Defer) { + qCDebug(lcQIORing) << "Request for" << it->operation() + << "had to be deferred, will not queue any more requests at the moment."; + break; + } + if (result == RequestPrepResult::RequestCompleted) { + addrItMap.remove(std::addressof(*it)); + it = pendingRequests.erase(it); // Completed synchronously, either failure or success. + continue; + } + anyQueued = true; + it->setQueued(true); + + sqIndexArray[index] = index; + ++inFlightRequests; + ++unstagedRequests; + ++tail; + ++it; + } + if (it != end) + lastUnqueuedIterator = it; + + if (anyQueued) { + qCDebug(lcQIORing, "Queued %u operation(s)", + tail - __atomic_load_n(sqTail, __ATOMIC_RELAXED)); + __atomic_store_n(sqTail, tail, __ATOMIC_RELEASE); + } +} + +static io_uring_op toUringOp(QIORing::Operation op) +{ + switch (op) { + case QIORing::Operation::Open: + return IORING_OP_OPENAT; + case QIORing::Operation::Read: + return IORING_OP_READ; + case QIORing::Operation::Close: + return IORING_OP_CLOSE; + case QIORing::Operation::Write: + return IORING_OP_WRITE; + case QIORing::Operation::VectoredRead: + return IORING_OP_READV; + case QIORing::Operation::VectoredWrite: + return IORING_OP_WRITEV; + case QIORing::Operation::Flush: + return IORING_OP_FSYNC; + case QIORing::Operation::Cancel: + return IORING_OP_ASYNC_CANCEL; + case QIORing::Operation::Stat: + return IORING_OP_STATX; + case QIORing::Operation::NumOperations: + break; + } + Q_UNREACHABLE_RETURN(IORING_OP_NOP); +} + +Q_ALWAYS_INLINE +static void prepareFileIOCommon(io_uring_sqe *sqe, const QIORingRequestOffsetFdBase &request) +{ + sqe->fd = qint32(request.fd); + sqe->off = request.offset; +} + +Q_ALWAYS_INLINE +static void prepareFileReadWrite(io_uring_sqe *sqe, const QIORingRequestOffsetFdBase &request, + const void *address, qsizetype size) +{ + prepareFileIOCommon(sqe, request); + sqe->len = quint32(size); + sqe->addr = quint64(address); +} + +// @todo: stolen from qfsfileengine_unix.cpp +static inline int openModeToOpenFlags(QIODevice::OpenMode mode) +{ + int oflags = QT_OPEN_RDONLY; +#ifdef QT_LARGEFILE_SUPPORT + oflags |= QT_OPEN_LARGEFILE; +#endif + + if ((mode & QIODevice::ReadWrite) == QIODevice::ReadWrite) + oflags = QT_OPEN_RDWR; + else if (mode & QIODevice::WriteOnly) + oflags = QT_OPEN_WRONLY; + + if ((mode & QIODevice::WriteOnly) + && !(mode & QIODevice::ExistingOnly)) // QFSFileEnginePrivate::openModeCanCreate(mode)) + oflags |= QT_OPEN_CREAT; + + if (mode & QIODevice::Truncate) + oflags |= QT_OPEN_TRUNC; + + if (mode & QIODevice::Append) + oflags |= QT_OPEN_APPEND; + + if (mode & QIODevice::NewOnly) + oflags |= QT_OPEN_EXCL; + + return oflags; +} + +auto QIORing::prepareRequest(io_uring_sqe *sqe, GenericRequestType &request) -> RequestPrepResult +{ + sqe->user_data = qint64(&request); + sqe->opcode = toUringOp(request.operation()); + + if (!verifyFd(request)) { + finishRequestWithError(request, QFileDevice::OpenError); + return RequestPrepResult::RequestCompleted; + } + + switch (request.operation()) { + case Operation::Open: { + const QIORingRequest<Operation::Open> + *openRequest = request.template requestData<Operation::Open>(); + sqe->fd = AT_FDCWD; // Could also support proper openat semantics + sqe->addr = reinterpret_cast<quint64>(openRequest->path.native().c_str()); + sqe->open_flags = openModeToOpenFlags(openRequest->flags); + auto &mode = sqe->len; + mode = 0666; // With an explicit API we can use QtPrivate::toMode_t() for this + break; + } + case Operation::Close: { + if (ongoingSplitOperations) + return Defer; + const QIORingRequest<Operation::Close> + *closeRequest = request.template requestData<Operation::Close>(); + sqe->fd = closeRequest->fd; + // Force all earlier entries in the sq to finish before this is processed: + sqe->flags |= IOSQE_IO_DRAIN; + break; + } + case Operation::Read: { + const QIORingRequest<Operation::Read> + *readRequest = request.template requestData<Operation::Read>(); + auto span = readRequest->destination; + if (span.size() >= MaxReadWriteLen) { + auto *extra = request.getOrInitializeExtra<QtPrivate::ReadWriteExtra>(); + qsizetype remaining = span.size() - extra->spanOffset; + span.slice(extra->spanOffset, std::min(remaining, MaxReadWriteLen)); + ++ongoingSplitOperations; + } + prepareFileReadWrite(sqe, *readRequest, span.data(), span.size()); + break; + } + case Operation::Write: { + const QIORingRequest<Operation::Write> + *writeRequest = request.template requestData<Operation::Write>(); + auto span = writeRequest->source; + if (span.size() >= MaxReadWriteLen) { + auto *extra = request.getOrInitializeExtra<QtPrivate::ReadWriteExtra>(); + qsizetype remaining = span.size() - extra->spanOffset; + span.slice(extra->spanOffset, std::min(remaining, MaxReadWriteLen)); + ++ongoingSplitOperations; + } + prepareFileReadWrite(sqe, *writeRequest, span.data(), span.size()); + break; + } + case Operation::VectoredRead: { + // @todo Apply the split read/write concept that will apply above to this too + const QIORingRequest<Operation::VectoredRead> + *readvRequest = request.template requestData<Operation::VectoredRead>(); + prepareFileReadWrite(sqe, *readvRequest, readvRequest->destinations.data(), + readvRequest->destinations.size()); + break; + } + case Operation::VectoredWrite: { + // @todo Apply the split read/write concept that will apply above to this too + const QIORingRequest<Operation::VectoredWrite> + *writevRequest = request.template requestData<Operation::VectoredWrite>(); + prepareFileReadWrite(sqe, *writevRequest, writevRequest->sources.data(), + writevRequest->sources.size()); + break; + } + case Operation::Flush: { + if (ongoingSplitOperations) + return Defer; + const QIORingRequest<Operation::Flush> + *flushRequest = request.template requestData<Operation::Flush>(); + sqe->fd = qint32(flushRequest->fd); + // Force all earlier entries in the sq to finish before this is processed: + sqe->flags |= IOSQE_IO_DRAIN; + flushInProgress = true; + break; + } + case Operation::Cancel: { + const QIORingRequest<Operation::Cancel> + *cancelRequest = request.template requestData<Operation::Cancel>(); + auto *otherOperation = reinterpret_cast<GenericRequestType *>(cancelRequest->handle); + auto it = std::as_const(addrItMap).find(otherOperation); + if (it == addrItMap.cend()) { // : The request to cancel doesn't exist + invokeCallback(*cancelRequest); + return RequestPrepResult::RequestCompleted; + } + if (!otherOperation->wasQueued()) { + // The request hasn't been queued yet, so we can just drop it from + // the pending requests and call the callback. + Q_ASSERT(!lastUnqueuedIterator); + finishRequestWithError(*otherOperation, QFileDevice::AbortError); + pendingRequests.erase(*it); // otherOperation is deleted + addrItMap.erase(it); + invokeCallback(*cancelRequest); + return RequestPrepResult::RequestCompleted; + } + sqe->addr = quint64(otherOperation); + break; + } + case Operation::Stat: { + const QIORingRequest<Operation::Stat> + *statRequest = request.template requestData<Operation::Stat>(); + // We need to store the statx struct somewhere: + struct statx *st = request.getOrInitializeExtra<struct statx>(); + + sqe->fd = statRequest->fd; + // We want to use the fd as the target of query instead of as the fd of the relative dir, + // so we set addr to an empty string, and specify the AT_EMPTY_PATH flag. + static const char emptystr[] = ""; + sqe->addr = qint64(emptystr); + sqe->statx_flags = AT_EMPTY_PATH; + sqe->len = STATX_ALL; // @todo configure somehow + sqe->off = quint64(st); + break; + } + case Operation::NumOperations: + Q_UNREACHABLE_RETURN(RequestPrepResult::RequestCompleted); + break; + } + return RequestPrepResult::Ok; +} + +void QIORing::GenericRequestType::cleanupExtra(Operation op, void *extra) +{ + switch (op) { + case Operation::Open: + case Operation::Close: + case Operation::VectoredRead: + case Operation::VectoredWrite: + case Operation::Cancel: + case Operation::Flush: + case Operation::NumOperations: + break; + case Operation::Read: + case Operation::Write: + delete static_cast<QtPrivate::ReadWriteExtra *>(extra); + return; + case Operation::Stat: + delete static_cast<struct statx *>(extra); + return; + } +} + +QT_END_NAMESPACE diff --git a/src/corelib/io/qioring_p.h b/src/corelib/io/qioring_p.h new file mode 100644 index 00000000000..0db832bc6bf --- /dev/null +++ b/src/corelib/io/qioring_p.h @@ -0,0 +1,521 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default + +#ifndef IOPROCESSOR_P_H +#define IOPROCESSOR_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/private/qtcoreglobal_p.h> + +#include <QtCore/qstring.h> +#include <QtCore/qspan.h> +#include <QtCore/qhash.h> +#include <QtCore/qfiledevice.h> +#include <QtCore/qloggingcategory.h> +#include <QtCore/qdeadlinetimer.h> + +#ifdef Q_OS_LINUX +# include <QtCore/qsocketnotifier.h> +struct io_uring_sqe; +struct io_uring_cqe; +#elif defined(Q_OS_WIN) +# include <QtCore/qwineventnotifier.h> +# include <qt_windows.h> +# include <ioringapi.h> +#endif + +#include <algorithm> +#include <filesystem> +#include <QtCore/qxpfunctional.h> +#include <variant> +#include <optional> +#include <type_traits> + +/* + This file defines an interface for the backend of QRandomAccessFile. + The backends themselves are implemented in platform-specific files, such as + ioring_linux.cpp, ioring_win.cpp, etc. + And has a lower-level interface than the public interface will have, but the + separation hopefully makes it easier to implement the ioring backends, test + them, and tweak them without the higher-level interface needing to see + changes, and to make it possible to tweak the higher-level interface without + needing to touch the (somewhat similar) ioring backends. + + Most of the interface is just an enum QIORing::Operation + the + QIORingRequest template class, which is specialized for each operation so it + carries just the relevant data for that operation. And a small mechanism to + store the request in a generic manner so they can be used in the + implementation files at the cost of some overhead. + + There will be absolutely zero binary compatibility guarantees for this + interface. +*/ + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(lcQIORing); + +namespace QtPrivate { +Q_NAMESPACE + +#define FOREACH_IO_OPERATION(OP) \ + OP(Open) \ + OP(Close) \ + OP(Read) \ + OP(Write) \ + OP(VectoredRead) \ + OP(VectoredWrite) \ + OP(Flush) \ + OP(Stat) \ + OP(Cancel) \ + /**/ +#define DEFINE_ENTRY(OP) OP, + +// clang-format off +enum class Operation : quint8 { + FOREACH_IO_OPERATION(DEFINE_ENTRY) + + NumOperations, +}; +// clang-format on +Q_ENUM_NS(Operation); +#undef DEFINE_ENTRY +}; // namespace QtPrivate + +template <QtPrivate::Operation Op> +struct QIORingRequest; + +class QIORing final +{ + class GenericRequestType; + struct RequestHandleTag; // Just used as an opaque pointer +public: + static constexpr quint32 DefaultSubmissionQueueSize = 128; + static constexpr quint32 DefaultCompletionQueueSize = DefaultSubmissionQueueSize * 2; + using Operation = QtPrivate::Operation; + using RequestHandle = RequestHandleTag *; + + Q_CORE_EXPORT + explicit QIORing(quint32 submissionQueueSize = DefaultSubmissionQueueSize, + quint32 completionQueueSize = DefaultCompletionQueueSize); + Q_CORE_EXPORT + ~QIORing(); + Q_DISABLE_COPY_MOVE(QIORing) + + Q_CORE_EXPORT + static QIORing *sharedInstance(); + bool ensureInitialized() { return initializeIORing(); } + + Q_CORE_EXPORT + static bool supportsOperation(Operation op); + template <Operation Op> + QIORing::RequestHandle queueRequest(QIORingRequest<Op> &&request) + { + Q_ASSERT(supportsOperation(Op)); + auto &r = pendingRequests.emplace_back(std::move(request)); + addrItMap.emplace(&r, std::prev(pendingRequests.end())); + if (queueRequestInternal(r) == QueuedRequestStatus::CompletedImmediately) + return nullptr; // Return an invalid handle, to avoid ABA with following requests + return reinterpret_cast<RequestHandle>(&r); + } + Q_CORE_EXPORT + void submitRequests(); + Q_CORE_EXPORT + bool waitForRequest(RequestHandle handle, QDeadlineTimer deadline = QDeadlineTimer::Forever); + + quint32 submissionQueueSize() const noexcept { return sqEntries; } + quint32 completionQueueSize() const noexcept { return cqEntries; } + +private: + std::list<GenericRequestType> pendingRequests; + using PendingRequestsIterator = decltype(pendingRequests.begin()); + QHash<void *, PendingRequestsIterator> addrItMap; + std::optional<PendingRequestsIterator> lastUnqueuedIterator; + quint32 sqEntries = 0; + quint32 cqEntries = 0; + quint32 inFlightRequests = 0; + quint32 unstagedRequests = 0; + bool stagePending = false; + bool preparingRequests = false; + qsizetype ongoingSplitOperations = 0; + + Q_CORE_EXPORT + bool initializeIORing(); + + enum class QueuedRequestStatus : bool { + Pending = false, + CompletedImmediately = true, + }; + Q_CORE_EXPORT + QueuedRequestStatus queueRequestInternal(GenericRequestType &request); + void prepareRequests(); + void completionReady(); + bool waitForCompletions(QDeadlineTimer deadline); + + template <typename Fun> + static auto invokeOnOp(GenericRequestType &req, Fun fn); + + template <Operation Op> + static void setFileErrorResult(QIORingRequest<Op> &req, QFileDevice::FileError error) + { + req.result.template emplace<QFileDevice::FileError>(error); + } + static void setFileErrorResult(GenericRequestType &req, QFileDevice::FileError error); + static void finishRequestWithError(GenericRequestType &req, QFileDevice::FileError error); + static bool verifyFd(GenericRequestType &req); + + enum RequestPrepResult : quint8 { + Ok, + QueueFull, + Defer, + RequestCompleted, + }; + enum class ReadWriteStatus : bool { + MoreToDo, + Finished, + }; +#ifdef Q_OS_LINUX + std::optional<QSocketNotifier> notifier; + // io_uring 'sq', 'sqe', 'cq', and 'cqe' pointers: + void *submissionQueue = nullptr; + io_uring_sqe *submissionQueueEntries = nullptr; + const io_uring_cqe *completionQueueEntries = nullptr; + + // Some pointers for working with the ring-buffer. + // The pointers to const are controlled by the kernel. + const quint32 *sqHead = nullptr; + quint32 *sqTail = nullptr; + const quint32 *sqIndexMask = nullptr; + quint32 *sqIndexArray = nullptr; + quint32 *cqHead = nullptr; + const quint32 *cqTail = nullptr; + const quint32 *cqIndexMask = nullptr; + // Because we want the flush to act as a barrier operation we need to track + // if there is one currently in progress. With kernel 6.16+ this seems to be + // fixed, but since we support older kernels we implement this deferring + // ourselves. + bool flushInProgress = false; + + int io_uringFd = -1; + int eventDescriptor = -1; + [[nodiscard]] + RequestPrepResult prepareRequest(io_uring_sqe *sqe, GenericRequestType &request); + template <Operation Op> + ReadWriteStatus handleReadCompletion(const io_uring_cqe *cqe, GenericRequestType *request); + template <Operation Op> + ReadWriteStatus handleWriteCompletion(const io_uring_cqe *cqe, GenericRequestType *request); +#elif defined(Q_OS_WIN) + // We use UINT32 because that's the type used for size parameters in their API. + static constexpr qsizetype MaxReadWriteLen = std::numeric_limits<UINT32>::max(); + std::optional<QWinEventNotifier> notifier; + HIORING ioRingHandle = nullptr; + HANDLE eventHandle = INVALID_HANDLE_VALUE; + + bool initialized = false; + bool queueWasFull = false; + [[nodiscard]] + RequestPrepResult prepareRequest(GenericRequestType &request); + QIORing::ReadWriteStatus handleReadCompletion( + HRESULT result, quintptr information, QSpan<std::byte> *destinations, void *voidExtra, + qxp::function_ref<qint64(std::variant<QFileDevice::FileError, qint64>)> setResult); + template <Operation Op> + ReadWriteStatus handleReadCompletion(const IORING_CQE *cqe, GenericRequestType *request); + ReadWriteStatus handleWriteCompletion( + HRESULT result, quintptr information, const QSpan<const std::byte> *sources, + void *voidExtra, + qxp::function_ref<qint64(std::variant<QFileDevice::FileError, qint64>)> setResult); + template <Operation Op> + ReadWriteStatus handleWriteCompletion(const IORING_CQE *cqe, GenericRequestType *request); +#endif +}; + +struct QIORingRequestEmptyBase +{ +}; + +template <QtPrivate::Operation Op> +struct QIORingResult; +template <QtPrivate::Operation Op> +struct QIORingRequest; + +// @todo: q23::expected once emplace() returns a reference +template <QtPrivate::Operation Op> +using ExpectedResultType = std::variant<std::monostate, QIORingResult<Op>, QFileDevice::FileError>; + +struct QIORingRequestOffsetFdBase : QIORingRequestEmptyBase +{ + qintptr fd; + quint64 offset; +}; + +template <QtPrivate::Operation Op, typename Base = QIORingRequestOffsetFdBase> +struct QIORingRequestBase : Base +{ + ExpectedResultType<Op> result; // To be filled in by the backend + QtPrivate::SlotObjUniquePtr callback; + template <typename Func> + Q_ALWAYS_INLINE void setCallback(Func &&func) + { + using Prototype = void (*)(const QIORingRequest<Op> &); + callback.reset(QtPrivate::makeCallableObject<Prototype>(std::forward<Func>(func))); + } +}; + +template <> +struct QIORingResult<QtPrivate::Operation::Open> +{ + // On Windows this is a HANDLE + qintptr fd; +}; +template <> +struct QIORingRequest<QtPrivate::Operation::Open> final + : QIORingRequestBase<QtPrivate::Operation::Open, QIORingRequestEmptyBase> +{ + std::filesystem::path path; + QFileDevice::OpenMode flags; +}; +template <> +struct QIORingResult<QtPrivate::Operation::Close> +{ +}; +template <> +struct QIORingRequest<QtPrivate::Operation::Close> final + : QIORingRequestBase<QtPrivate::Operation::Close, QIORingRequestEmptyBase> +{ + // On Windows this is a HANDLE + qintptr fd; +}; + +template <> +struct QIORingResult<QtPrivate::Operation::Write> +{ + qint64 bytesWritten; +}; +template <> +struct QIORingRequest<QtPrivate::Operation::Write> final + : QIORingRequestBase<QtPrivate::Operation::Write> +{ + QSpan<const std::byte> source; +}; +template <> +struct QIORingResult<QtPrivate::Operation::VectoredWrite> final + : QIORingResult<QtPrivate::Operation::Write> +{ +}; +template <> +struct QIORingRequest<QtPrivate::Operation::VectoredWrite> final + : QIORingRequestBase<QtPrivate::Operation::VectoredWrite> +{ + QSpan<const QSpan<const std::byte>> sources; +}; + +template <> +struct QIORingResult<QtPrivate::Operation::Read> +{ + qint64 bytesRead; +}; +template <> +struct QIORingRequest<QtPrivate::Operation::Read> final + : QIORingRequestBase<QtPrivate::Operation::Read> +{ + QSpan<std::byte> destination; +}; + +template <> +struct QIORingResult<QtPrivate::Operation::VectoredRead> final + : QIORingResult<QtPrivate::Operation::Read> +{ +}; +template <> +struct QIORingRequest<QtPrivate::Operation::VectoredRead> final + : QIORingRequestBase<QtPrivate::Operation::VectoredRead> +{ + QSpan<QSpan<std::byte>> destinations; +}; + +template <> +struct QIORingResult<QtPrivate::Operation::Flush> final +{ + // No value in the result, just a success or failure +}; +template <> +struct QIORingRequest<QtPrivate::Operation::Flush> final : QIORingRequestBase<QtPrivate::Operation::Flush, QIORingRequestEmptyBase> +{ + // On Windows this is a HANDLE + qintptr fd; +}; + +template <> +struct QIORingResult<QtPrivate::Operation::Stat> final +{ + quint64 size; +}; +template <> +struct QIORingRequest<QtPrivate::Operation::Stat> final + : QIORingRequestBase<QtPrivate::Operation::Stat, QIORingRequestEmptyBase> +{ + // On Windows this is a HANDLE + qintptr fd; +}; + +// This is not inheriting the QIORingRequestBase because it doesn't have a result, +// whether it was successful or not is indicated by whether the operation +// it was cancelling was successful or not. +template <> +struct QIORingRequest<QtPrivate::Operation::Cancel> final : QIORingRequestEmptyBase +{ + QIORing::RequestHandle handle; + QtPrivate::SlotObjUniquePtr callback; + template <typename Func> + Q_ALWAYS_INLINE void setCallback(Func &&func) + { + using Op = QtPrivate::Operation; + using Prototype = void (*)(const QIORingRequest<Op::Cancel> &); + callback.reset(QtPrivate::makeCallableObject<Prototype>(std::forward<Func>(func))); + } +}; + +template <QIORing::Operation Op> +Q_ALWAYS_INLINE void invokeCallback(const QIORingRequest<Op> &request) +{ + if (!request.callback) + return; + void *args[2] = { nullptr, const_cast<QIORingRequest<Op> *>(&request) }; + request.callback->call(nullptr, args); +} + +class QIORing::GenericRequestType +{ + friend class QIORing; + +#define POPULATE_VARIANT(Op) \ + QIORingRequest<Operation::Op>, \ + /**/ + + std::variant< + FOREACH_IO_OPERATION(POPULATE_VARIANT) + std::monostate + > taggedUnion; + +#undef POPULATE_VARIANT + + void *extraData = nullptr; + bool queued = false; + + template <Operation Op> + Q_ALWAYS_INLINE void initializeStorage(QIORingRequest<Op> &&t) noexcept + { + static_assert(Op < Operation::NumOperations); + taggedUnion.emplace<QIORingRequest<Op>>(std::move(t)); + } + + Q_CORE_EXPORT + static void cleanupExtra(Operation op, void *extra); + template <typename T> + T *getOrInitializeExtra() + { + if (!extraData) + extraData = new T(); + return static_cast<T *>(extraData); + } + template <typename T> + T *getExtra() const + { + return static_cast<T *>(extraData); + } + void reset() noexcept + { + Operation op = operation(); + taggedUnion.emplace<std::monostate>(); + if (extraData) + cleanupExtra(op, std::exchange(extraData, nullptr)); + } + +public: + template <Operation Op> + explicit GenericRequestType(QIORingRequest<Op> &&t) noexcept + { + initializeStorage(std::move(t)); + } + ~GenericRequestType() noexcept + { + reset(); + } + Q_DISABLE_COPY_MOVE(GenericRequestType) + // We have to provide equality operators. Since copying is disabled, we just check for equality + // based on the address in memory. Two requests could be constructed to be equal, but we don't + // actually care because the order in which they are added to the queue may also matter. + friend bool operator==(const GenericRequestType &l, const GenericRequestType &r) noexcept + { + return std::addressof(l) == std::addressof(r); + } + friend bool operator!=(const GenericRequestType &l, const GenericRequestType &r) noexcept + { + return !(l == r); + } + + Operation operation() const { return Operation(taggedUnion.index()); } + template <Operation Op> + QIORingRequest<Op> *requestData() + { + if (operation() == Op) + return std::get_if<QIORingRequest<Op>>(&taggedUnion); + Q_ASSERT("Wrong operation requested, see operation()"); + return nullptr; + } + template <Operation Op> + QIORingRequest<Op> takeRequestData() + { + if (operation() == Op) + return std::move(*std::get_if<QIORingRequest<Op>>(&taggedUnion)); + Q_ASSERT("Wrong operation requested, see operation()"); + return {}; + } + bool wasQueued() const { return queued; } + void setQueued(bool status) { queued = status; } +}; + +template <typename Fun> +auto QIORing::invokeOnOp(GenericRequestType &req, Fun fn) +{ +#define INVOKE_ON_OP(Op) \ +case QIORing::Operation::Op: \ + fn(req.template requestData<Operation::Op>()); \ + return; \ + /**/ + + switch (req.operation()) { + FOREACH_IO_OPERATION(INVOKE_ON_OP) + case QIORing::Operation::NumOperations: + break; + } + + Q_UNREACHABLE(); +#undef INVOKE_ON_OP +} + +namespace QtPrivate { +// The 'extra' struct for Read/Write operations that must be split up +struct ReadWriteExtra +{ + qint64 totalProcessed = 0; + qsizetype spanIndex = 0; + qsizetype spanOffset = 0; + qsizetype numSpans = 1; +}; +} // namespace QtPrivate + +QT_END_NAMESPACE + +#endif // IOPROCESSOR_P_H diff --git a/src/corelib/io/qioring_win.cpp b/src/corelib/io/qioring_win.cpp new file mode 100644 index 00000000000..42c51f428d6 --- /dev/null +++ b/src/corelib/io/qioring_win.cpp @@ -0,0 +1,754 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default + +#include "qioring_p.h" + +QT_REQUIRE_CONFIG(windows_ioring); + +#include <QtCore/qcompilerdetection.h> +#include <QtCore/qobject.h> +#include <QtCore/qscopedvaluerollback.h> + +#include <qt_windows.h> +#include <ioringapi.h> + +#include <QtCore/q26numeric.h> + +QT_BEGIN_NAMESPACE + +// We don't really build for 32-bit windows anymore, but this code is definitely wrong if someone +// does. +static_assert(sizeof(qsizetype) > sizeof(UINT32), + "This code is written with assuming 64-bit Windows."); + +using namespace Qt::StringLiterals; + +static HRESULT buildReadOperation(HIORING ioRingHandle, qintptr fd, QSpan<std::byte> destination, + quint64 offset, quintptr userData) +{ + // NOLINTNEXTLINE(performance-no-int-to-ptr) + const IORING_HANDLE_REF fileRef((HANDLE(fd))); + const IORING_BUFFER_REF bufferRef(destination.data()); + const auto maxSize = q26::saturate_cast<UINT32>(destination.size()); + Q_ASSERT(maxSize == destination.size()); + return BuildIoRingReadFile(ioRingHandle, fileRef, bufferRef, maxSize, offset, userData, + IOSQE_FLAGS_NONE); +} + +static HRESULT buildWriteOperation(HIORING ioRingHandle, qintptr fd, QSpan<const std::byte> source, + quint64 offset, quintptr userData) +{ + // NOLINTNEXTLINE(performance-no-int-to-ptr) + const IORING_HANDLE_REF fileRef((HANDLE(fd))); + const IORING_BUFFER_REF bufferRef(const_cast<std::byte *>(source.data())); + const auto maxSize = q26::saturate_cast<UINT32>(source.size()); + Q_ASSERT(maxSize == source.size()); + // @todo: FILE_WRITE_FLAGS can be set to write-through, could be used for Unbuffered mode. + return BuildIoRingWriteFile(ioRingHandle, fileRef, bufferRef, maxSize, offset, + FILE_WRITE_FLAGS_NONE, userData, IOSQE_FLAGS_NONE); +} + +QIORing::~QIORing() +{ + if (initialized) { + CloseHandle(eventHandle); + CloseIoRing(ioRingHandle); + } +} + +bool QIORing::initializeIORing() +{ + if (initialized) + return true; + + IORING_CAPABILITIES capabilities; + QueryIoRingCapabilities(&capabilities); + if (capabilities.MaxVersion < IORING_VERSION_3) // 3 adds write, flush and drain + return false; + if ((capabilities.FeatureFlags & IORING_FEATURE_SET_COMPLETION_EVENT) == 0) + return false; // We currently require the SET_COMPLETION_EVENT feature + + qCDebug(lcQIORing) << "Creating QIORing, requesting space for" << sqEntries + << "submission queue entries, and" << cqEntries + << "completion queue entries"; + + IORING_CREATE_FLAGS flags; + memset(&flags, 0, sizeof(flags)); + HRESULT hr = CreateIoRing(IORING_VERSION_3, flags, sqEntries, cqEntries, &ioRingHandle); + if (FAILED(hr)) { + qErrnoWarning(hr, "failed to initialize QIORing"); + return false; + } + auto earlyExitCleanup = qScopeGuard([this]() { + if (eventHandle != INVALID_HANDLE_VALUE) + CloseHandle(eventHandle); + CloseIoRing(ioRingHandle); + }); + eventHandle = CreateEvent(nullptr, TRUE, FALSE, nullptr); + if (eventHandle == INVALID_HANDLE_VALUE) { + qErrnoWarning("Failed to create event handle"); + return false; + } + notifier.emplace(eventHandle); + hr = SetIoRingCompletionEvent(ioRingHandle, eventHandle); + if (FAILED(hr)) { + qErrnoWarning(hr, "Failed to assign the event handle to QIORing"); + return false; + } + IORING_INFO info; + if (SUCCEEDED(GetIoRingInfo(ioRingHandle, &info))) { + sqEntries = info.SubmissionQueueSize; + cqEntries = info.CompletionQueueSize; + qCDebug(lcQIORing) << "QIORing configured with capacity for" << sqEntries + << "submissions, and" << cqEntries << "completions."; + } + QObject::connect(std::addressof(*notifier), &QWinEventNotifier::activated, + std::addressof(*notifier), [this]() { completionReady(); }); + initialized = true; + earlyExitCleanup.dismiss(); + return true; +} + +QIORing::ReadWriteStatus QIORing::handleReadCompletion( + HRESULT result, quintptr information, QSpan<std::byte> *destinations, void *voidExtra, + qxp::function_ref<qint64(std::variant<QFileDevice::FileError, qint64>)> setResultFn) +{ + if (FAILED(result)) { + if (result == HRESULT_FROM_WIN32(ERROR_HANDLE_EOF)) + return ReadWriteStatus::Finished; + + if (result == E_ABORT) + setResultFn(QFileDevice::AbortError); + else + setResultFn(QFileDevice::ReadError); + } else if (auto *extra = static_cast<QtPrivate::ReadWriteExtra *>(voidExtra)) { + const qsizetype bytesRead = q26::saturate_cast<decltype(MaxReadWriteLen)>(information); + qCDebug(lcQIORing) << "Partial read of" << bytesRead << "bytes completed"; + extra->totalProcessed = setResultFn(bytesRead); + extra->spanOffset += bytesRead; + qCDebug(lcQIORing) << "Read operation progress: span" << extra->spanIndex << "offset" + << extra->spanOffset << "of" << destinations[extra->spanIndex].size() + << "bytes. Total read:" << extra->totalProcessed << "bytes"; + // The while loop is in case there is an empty span, we skip over it: + while (extra->spanOffset == destinations[extra->spanIndex].size()) { + // Move to next span + if (++extra->spanIndex == extra->numSpans) + return ReadWriteStatus::Finished; + extra->spanOffset = 0; + } + return ReadWriteStatus::MoreToDo; + } else { + setResultFn(q26::saturate_cast<decltype(MaxReadWriteLen)>(information)); + } + return ReadWriteStatus::Finished; +} + +template <QIORing::Operation Op> +Q_ALWAYS_INLINE QIORing::ReadWriteStatus QIORing::handleReadCompletion(const IORING_CQE *cqe, + GenericRequestType *request) +{ + static_assert(Op == Operation::Read || Op == Operation::VectoredRead); + QIORingRequest<Op> *readRequest = request->requestData<Op>(); + Q_ASSERT(readRequest); + auto *destinations = [&readRequest]() { + if constexpr (Op == Operation::Read) + return &readRequest->destination; + else + return &readRequest->destinations[0]; + }(); + auto setResult = [readRequest](const std::variant<QFileDevice::FileError, qint64> &result) { + if (result.index() == 0) { // error + QIORing::setFileErrorResult(*readRequest, *std::get_if<QFileDevice::FileError>(&result)); + return 0ll; + } + // else: success + auto &readResult = [&readRequest]() -> QIORingResult<Op> & { + if (auto *result = std::get_if<QIORingResult<Op>>(&readRequest->result)) + return *result; + return readRequest->result.template emplace<QIORingResult<Op>>(); + }(); + qint64 bytesRead = *std::get_if<qint64>(&result); + readResult.bytesRead += bytesRead; + return readResult.bytesRead; + }; + QIORing::ReadWriteStatus rwstatus = handleReadCompletion( + cqe->ResultCode, cqe->Information, destinations, request->getExtra<void>(), setResult); + switch (rwstatus) { + case ReadWriteStatus::Finished: + if (request->getExtra<void>()) + --ongoingSplitOperations; + break; + case ReadWriteStatus::MoreToDo: { + // Move the request such that it is next in the list to be processed: + auto &it = addrItMap[request]; + const auto where = lastUnqueuedIterator.value_or(pendingRequests.end()); + pendingRequests.splice(where, pendingRequests, it); + it = std::prev(where); + lastUnqueuedIterator = it; + break; + } + } + return rwstatus; +} + +QIORing::ReadWriteStatus QIORing::handleWriteCompletion( + HRESULT result, quintptr information, const QSpan<const std::byte> *sources, void *voidExtra, + qxp::function_ref<qint64(std::variant<QFileDevice::FileError, qint64>)> setResultFn) +{ + if (FAILED(result)) { + if (result == E_ABORT) + setResultFn(QFileDevice::AbortError); + else + setResultFn(QFileDevice::WriteError); + } else if (auto *extra = static_cast<QtPrivate::ReadWriteExtra *>(voidExtra)) { + const qsizetype bytesWritten = q26::saturate_cast<decltype(MaxReadWriteLen)>(information); + qCDebug(lcQIORing) << "Partial write of" << bytesWritten << "bytes completed"; + extra->totalProcessed = setResultFn(bytesWritten); + extra->spanOffset += bytesWritten; + qCDebug(lcQIORing) << "Write operation progress: span" << extra->spanIndex << "offset" + << extra->spanOffset << "of" << sources[extra->spanIndex].size() + << "bytes. Total written:" << extra->totalProcessed << "bytes"; + // The while loop is in case there is an empty span, we skip over it: + while (extra->spanOffset == sources[extra->spanIndex].size()) { + // Move to next span + if (++extra->spanIndex == extra->numSpans) + return ReadWriteStatus::Finished; + extra->spanOffset = 0; + } + return ReadWriteStatus::MoreToDo; + } else { + setResultFn(q26::saturate_cast<decltype(MaxReadWriteLen)>(information)); + } + return ReadWriteStatus::Finished; +} + +template <QIORing::Operation Op> +Q_ALWAYS_INLINE QIORing::ReadWriteStatus QIORing::handleWriteCompletion(const IORING_CQE *cqe, + GenericRequestType *request) +{ + static_assert(Op == Operation::Write || Op == Operation::VectoredWrite); + QIORingRequest<Op> *writeRequest = request->requestData<Op>(); + auto *sources = [&writeRequest]() { + if constexpr (Op == Operation::Write) + return &writeRequest->source; + else + return &writeRequest->sources[0]; + }(); + auto setResult = [writeRequest](const std::variant<QFileDevice::FileError, qint64> &result) { + if (result.index() == 0) { // error + QIORing::setFileErrorResult(*writeRequest, *std::get_if<QFileDevice::FileError>(&result)); + return 0ll; + } + // else: success + auto &writeResult = [&writeRequest]() -> QIORingResult<Op> & { + if (auto *result = std::get_if<QIORingResult<Op>>(&writeRequest->result)) + return *result; + return writeRequest->result.template emplace<QIORingResult<Op>>(); + }(); + qint64 bytesWritten = *std::get_if<qint64>(&result); + writeResult.bytesWritten += bytesWritten; + return writeResult.bytesWritten; + }; + QIORing::ReadWriteStatus rwstatus = handleWriteCompletion( + cqe->ResultCode, cqe->Information, sources, request->getExtra<void>(), setResult); + switch (rwstatus) { + case ReadWriteStatus::Finished: + if (request->getExtra<void>()) + --ongoingSplitOperations; + break; + case ReadWriteStatus::MoreToDo: { + // Move the request such that it is next in the list to be processed: + auto &it = addrItMap[request]; + const auto where = lastUnqueuedIterator.value_or(pendingRequests.end()); + pendingRequests.splice(where, pendingRequests, it); + it = std::prev(where); + lastUnqueuedIterator = it; + break; + } + } + return rwstatus; +} + +void QIORing::completionReady() +{ + ResetEvent(eventHandle); + IORING_CQE entry; + while (PopIoRingCompletion(ioRingHandle, &entry) == S_OK) { + // NOLINTNEXTLINE(performance-no-int-to-ptr) + auto *request = reinterpret_cast<GenericRequestType *>(entry.UserData); + if (!addrItMap.contains(request)) { + qCDebug(lcQIORing) << "Got completed entry, but cannot find it in the map. Likely " + "deleted, ignoring. UserData pointer:" + << request; + continue; + } + qCDebug(lcQIORing) << "Got completed entry. Operation:" << request->operation() + << "- UserData pointer:" << request + << "- Result:" << qt_error_string(entry.ResultCode) << '(' + << QByteArray("0x"_ba + QByteArray::number(entry.ResultCode, 16)).data() + << ')'; + switch (request->operation()) { + case Operation::Open: // Synchronously finishes + Q_UNREACHABLE_RETURN(); + case Operation::Close: { + auto closeRequest = request->takeRequestData<Operation::Close>(); + // We ignore the result of the flush, we are closing the handle anyway. + // NOLINTNEXTLINE(performance-no-int-to-ptr) + if (CloseHandle(HANDLE(closeRequest.fd))) + closeRequest.result.emplace<QIORingResult<Operation::Close>>(); + else + closeRequest.result.emplace<QFileDevice::FileError>(QFileDevice::OpenError); + invokeCallback(closeRequest); + break; + } + case Operation::Read: { + const ReadWriteStatus status = handleReadCompletion<Operation::Read>(&entry, request); + if (status == ReadWriteStatus::MoreToDo) + continue; + auto readRequest = request->takeRequestData<Operation::Read>(); + invokeCallback(readRequest); + break; + } + case Operation::Write: { + const ReadWriteStatus status = handleWriteCompletion<Operation::Write>(&entry, request); + if (status == ReadWriteStatus::MoreToDo) + continue; + auto writeRequest = request->takeRequestData<Operation::Write>(); + invokeCallback(writeRequest); + break; + } + case Operation::VectoredRead: { + const ReadWriteStatus status = handleReadCompletion<Operation::VectoredRead>(&entry, + request); + if (status == ReadWriteStatus::MoreToDo) + continue; + auto vectoredReadRequest = request->takeRequestData<Operation::VectoredRead>(); + invokeCallback(vectoredReadRequest); + break; + } + case Operation::VectoredWrite: { + const ReadWriteStatus status = handleWriteCompletion<Operation::VectoredWrite>(&entry, + request); + if (status == ReadWriteStatus::MoreToDo) + continue; + auto vectoredWriteRequest = request->takeRequestData<Operation::VectoredWrite>(); + invokeCallback(vectoredWriteRequest); + break; + } + case Operation::Flush: { + auto flushRequest = request->takeRequestData<Operation::Flush>(); + if (FAILED(entry.ResultCode)) { + qErrnoWarning(entry.ResultCode, "Flush operation failed"); + // @todo any FlushError? + flushRequest.result.emplace<QFileDevice::FileError>( + QFileDevice::FileError::WriteError); + } else { + flushRequest.result.emplace<QIORingResult<Operation::Flush>>(); + } + invokeCallback(flushRequest); + break; + } + case QtPrivate::Operation::Cancel: { + auto cancelRequest = request->takeRequestData<Operation::Cancel>(); + invokeCallback(cancelRequest); + break; + } + case QtPrivate::Operation::Stat: + Q_UNREACHABLE_RETURN(); // Completes synchronously + break; + case Operation::NumOperations: + Q_UNREACHABLE_RETURN(); + break; + } + auto it = addrItMap.take(request); + pendingRequests.erase(it); + --inFlightRequests; + queueWasFull = false; + } + prepareRequests(); + if (unstagedRequests > 0) + submitRequests(); +} + +bool QIORing::waitForCompletions(QDeadlineTimer deadline) +{ + notifier->setEnabled(false); + auto reactivateNotifier = qScopeGuard([this]() { + notifier->setEnabled(true); + }); + + while (!deadline.hasExpired()) { + DWORD timeout = 0; + if (deadline.isForever()) { + timeout = INFINITE; + } else { + timeout = q26::saturate_cast<DWORD>(deadline.remainingTime()); + if (timeout == INFINITE) + --timeout; + } + if (WaitForSingleObject(eventHandle, timeout) == WAIT_OBJECT_0) + return true; + } + return false; +} + +static HANDLE openFile(const QIORingRequest<QIORing::Operation::Open> &openRequest) +{ + DWORD access = 0; + if (openRequest.flags.testFlag(QIODevice::ReadOnly)) + access |= GENERIC_READ; + if (openRequest.flags.testFlag(QIODevice::WriteOnly)) + access |= GENERIC_WRITE; + + DWORD disposition = 0; + if (openRequest.flags.testFlag(QIODevice::Append)) { + qCWarning(lcQIORing, "Opening file with Append not supported for random access file"); + return INVALID_HANDLE_VALUE; + } + if (openRequest.flags.testFlag(QIODevice::NewOnly)) { + disposition = CREATE_NEW; + } else { + // If Write is specified we _may_ create a file. + // See qfsfileengine_p.h openModeCanCreate. + disposition = openRequest.flags.testFlag(QIODeviceBase::WriteOnly) + && !openRequest.flags.testFlags(QIODeviceBase::ExistingOnly) + ? OPEN_ALWAYS + : OPEN_EXISTING; + } + const DWORD shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE; + const DWORD flagsAndAttribs = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED; + HANDLE h = CreateFile(openRequest.path.native().c_str(), access, shareMode, nullptr, + disposition, flagsAndAttribs, nullptr); + if (h != INVALID_HANDLE_VALUE && openRequest.flags.testFlag(QIODeviceBase::Truncate)) { + FILE_END_OF_FILE_INFO info; + memset(&info, 0, sizeof(info)); + SetFileInformationByHandle(h, FileEndOfFileInfo, &info, sizeof(info)); + } + return h; +} + +bool QIORing::supportsOperation(Operation op) +{ + switch (op) { + case QtPrivate::Operation::Open: + case QtPrivate::Operation::Close: + case QtPrivate::Operation::Read: + case QtPrivate::Operation::Write: + case QtPrivate::Operation::Flush: + case QtPrivate::Operation::Cancel: + case QtPrivate::Operation::Stat: + case QtPrivate::Operation::VectoredRead: + case QtPrivate::Operation::VectoredWrite: + return true; + case QtPrivate::Operation::NumOperations: + return false; + } + return false; // Not unreachable, we could allow more for io_uring +} + +void QIORing::submitRequests() +{ + stagePending = false; + if (unstagedRequests == 0) + return; + + // We perform a miniscule wait - to see if anything already in the queue is already completed - + // if we have been told the queue is full. Then we can try queuing more things right away + const bool shouldTryWait = std::exchange(queueWasFull, false); + const auto submitToRing = [this, &shouldTryWait] { + quint32 submittedEntries = 0; + HRESULT hr = SubmitIoRing(ioRingHandle, shouldTryWait ? 1 : 0, 1, &submittedEntries); + qCDebug(lcQIORing) << "Submitted" << submittedEntries << "requests"; + unstagedRequests -= submittedEntries; + if (FAILED(hr)) { + // Too noisy, not a real problem + // qErrnoWarning(hr, "Failed to submit QIORing request: %u", submittedEntries); + return false; + } + return submittedEntries > 0; + }; + if (submitToRing() && shouldTryWait) { + // We try to prepare some more request and submit more if able + prepareRequests(); + if (unstagedRequests > 0) + submitToRing(); + } +} + +void QIORing::prepareRequests() +{ + if (!lastUnqueuedIterator) + return; + Q_ASSERT(!preparingRequests); + QScopedValueRollback<bool> prepareGuard(preparingRequests, true); + + auto it = *lastUnqueuedIterator; + lastUnqueuedIterator.reset(); + const auto end = pendingRequests.end(); + while (!queueWasFull && it != end) { + auto &request = *it; + switch (prepareRequest(request)) { + case RequestPrepResult::Ok: + ++unstagedRequests; + ++inFlightRequests; + break; + case RequestPrepResult::QueueFull: + qCDebug(lcQIORing) << "Queue was reported as full, in flight requests:" + << inFlightRequests << "submission queue size:" << sqEntries + << "completion queue size:" << cqEntries; + queueWasFull = true; + lastUnqueuedIterator = it; + return; + case RequestPrepResult::Defer: + qCDebug(lcQIORing) << "Request for" << request.operation() + << "had to be deferred, will not queue any more requests at the " + "moment."; + lastUnqueuedIterator = it; + return; // + case RequestPrepResult::RequestCompleted: + // Used for requests that immediately finish. So we erase it: + qCDebug(lcQIORing) << "Request for" << request.operation() + << "completed synchronously."; + addrItMap.remove(&request); + it = pendingRequests.erase(it); + continue; // Don't increment iterator again + } + ++it; + } +} + +namespace QtPrivate { +template <typename T> +using DetectHasFd = decltype(std::declval<const T &>().fd); + +template <typename T> +constexpr bool OperationHasFd_v = qxp::is_detected_v<DetectHasFd, T>; +} // namespace QtPrivate + +auto QIORing::prepareRequest(GenericRequestType &request) -> RequestPrepResult +{ + qCDebug(lcQIORing) << "Preparing a request with operation" << request.operation(); + HRESULT hr = -1; + + if (!verifyFd(request)) { + finishRequestWithError(request, QFileDevice::OpenError); + return RequestPrepResult::RequestCompleted; + } + + switch (request.operation()) { + case Operation::Open: { + QIORingRequest<Operation::Open> openRequest = request.takeRequestData<Operation::Open>(); + HANDLE fileDescriptor = openFile(openRequest); + if (fileDescriptor == INVALID_HANDLE_VALUE) { + openRequest.result.emplace<QFileDevice::FileError>(QFileDevice::FileError::OpenError); + } else { + auto &result = openRequest.result.emplace<QIORingResult<Operation::Open>>(); + result.fd = qintptr(fileDescriptor); + } + invokeCallback(openRequest); + return RequestPrepResult::RequestCompleted; + } + case Operation::Close: { + if (ongoingSplitOperations > 0) + return RequestPrepResult::Defer; + + // We need to wait until all previous OPS are done before we close the request. + // There is no no-op request in the Windows QIORing, so we issue a flush. + auto *closeRequest = request.requestData<Operation::Close>(); + // NOLINTNEXTLINE(performance-no-int-to-ptr) + const IORING_HANDLE_REF fileRef(HANDLE(closeRequest->fd)); + hr = BuildIoRingFlushFile(ioRingHandle, fileRef, FILE_FLUSH_MIN_METADATA, + quintptr(std::addressof(request)), + IOSQE_FLAGS_DRAIN_PRECEDING_OPS); + break; + } + case Operation::Read: { + auto *readRequest = request.requestData<Operation::Read>(); + auto span = readRequest->destination; + auto offset = readRequest->offset; + if (span.size() > MaxReadWriteLen) { + qCDebug(lcQIORing) << "Requested Read of size" << span.size() << "has to be split"; + auto *extra = request.getOrInitializeExtra<QtPrivate::ReadWriteExtra>(); + if (extra->spanOffset == 0) + ++ongoingSplitOperations; + const qsizetype remaining = span.size() - extra->spanOffset; + span.slice(extra->spanOffset, std::min(remaining, MaxReadWriteLen)); + offset += extra->totalProcessed; + } + hr = buildReadOperation(ioRingHandle, readRequest->fd, span, offset, + quintptr(std::addressof(request))); + break; + } + case Operation::VectoredRead: { + auto *vectoredReadRequest = request.requestData<Operation::VectoredRead>(); + auto span = vectoredReadRequest->destinations.front(); + auto offset = vectoredReadRequest->offset; + if (Q_LIKELY(vectoredReadRequest->destinations.size() > 1 + || span.size() > MaxReadWriteLen)) { + auto *extra = request.getOrInitializeExtra<QtPrivate::ReadWriteExtra>(); + if (extra->spanOffset == 0 && extra->spanIndex == 0) + ++ongoingSplitOperations; + extra->numSpans = vectoredReadRequest->destinations.size(); + + span = vectoredReadRequest->destinations[extra->spanIndex]; + + const qsizetype remaining = span.size() - extra->spanOffset; + span.slice(extra->spanOffset, std::min(remaining, MaxReadWriteLen)); + offset += extra->totalProcessed; + } + hr = buildReadOperation(ioRingHandle, vectoredReadRequest->fd, span, + offset, quintptr(std::addressof(request))); + break; + } + case Operation::Write: { + auto *writeRequest = request.requestData<Operation::Write>(); + auto span = writeRequest->source; + auto offset = writeRequest->offset; + if (span.size() > MaxReadWriteLen) { + qCDebug(lcQIORing) << "Requested Write of size" << span.size() << "has to be split"; + auto *extra = request.getOrInitializeExtra<QtPrivate::ReadWriteExtra>(); + if (extra->spanOffset == 0) + ++ongoingSplitOperations; + const qsizetype remaining = span.size() - extra->spanOffset; + span.slice(extra->spanOffset, std::min(remaining, MaxReadWriteLen)); + offset += extra->totalProcessed; + } + hr = buildWriteOperation(ioRingHandle, writeRequest->fd, span, offset, + quintptr(std::addressof(request))); + break; + } + case Operation::VectoredWrite: { + auto *vectoredWriteRequest = request.requestData<Operation::VectoredWrite>(); + auto span = vectoredWriteRequest->sources.front(); + auto offset = vectoredWriteRequest->offset; + if (Q_LIKELY(vectoredWriteRequest->sources.size() > 1 + || span.size() > MaxReadWriteLen)) { + auto *extra = request.getOrInitializeExtra<QtPrivate::ReadWriteExtra>(); + if (extra->spanOffset == 0 && extra->spanIndex == 0) + ++ongoingSplitOperations; + extra->numSpans = vectoredWriteRequest->sources.size(); + + span = vectoredWriteRequest->sources[extra->spanIndex]; + + const qsizetype remaining = span.size() - extra->spanOffset; + span.slice(extra->spanOffset, std::min(remaining, MaxReadWriteLen)); + offset += extra->totalProcessed; + } + hr = buildWriteOperation(ioRingHandle, vectoredWriteRequest->fd, span, + offset, quintptr(std::addressof(request))); + break; + } + case Operation::Flush: { + if (ongoingSplitOperations > 0) + return RequestPrepResult::Defer; + auto *flushRequest = request.requestData<Operation::Flush>(); + // NOLINTNEXTLINE(performance-no-int-to-ptr) + const IORING_HANDLE_REF fileRef(HANDLE(flushRequest->fd)); + hr = BuildIoRingFlushFile(ioRingHandle, fileRef, FILE_FLUSH_DEFAULT, + quintptr(std::addressof(request)), + IOSQE_FLAGS_DRAIN_PRECEDING_OPS); + break; + } + case QtPrivate::Operation::Stat: { + auto statRequest = request.takeRequestData<Operation::Stat>(); + FILE_STANDARD_INFO info; + // NOLINTNEXTLINE(performance-no-int-to-ptr) + if (!GetFileInformationByHandleEx(HANDLE(statRequest.fd), FileStandardInfo, &info, + sizeof(info))) { + DWORD winErr = GetLastError(); + QFileDevice::FileError error = QFileDevice::UnspecifiedError; + if (winErr == ERROR_FILE_NOT_FOUND || winErr == ERROR_INVALID_HANDLE) + error = QFileDevice::OpenError; + else if (winErr == ERROR_ACCESS_DENIED) + error = QFileDevice::PermissionsError; + statRequest.result.emplace<QFileDevice::FileError>(error); + } else { + auto &result = statRequest.result.emplace<QIORingResult<Operation::Stat>>(); + result.size = info.EndOfFile.QuadPart; + } + invokeCallback(statRequest); + return RequestPrepResult::RequestCompleted; + } + case Operation::Cancel: { + auto *cancelRequest = request.requestData<Operation::Cancel>(); + auto *otherOperation = reinterpret_cast<GenericRequestType *>(cancelRequest->handle); + if (!otherOperation || !addrItMap.contains(otherOperation)) { + qCDebug(lcQIORing, "Invalid cancel for non-existant operation"); + invokeCallback(*cancelRequest); + return RequestPrepResult::RequestCompleted; + } + qCDebug(lcQIORing) << "Cancelling operation of type" << otherOperation->operation() + << "which was" + << (otherOperation->wasQueued() ? "queued" : "not queued"); + Q_ASSERT(&request != otherOperation); + if (!otherOperation->wasQueued()) { + // The request hasn't been queued yet, so we can just drop it from + // the pending requests and call the callback. + auto it = addrItMap.take(otherOperation); + finishRequestWithError(*otherOperation, QFileDevice::AbortError); + pendingRequests.erase(it); // otherOperation is deleted + invokeCallback(*cancelRequest); + return RequestPrepResult::RequestCompleted; + } + qintptr fd = -1; + invokeOnOp(*otherOperation, [&fd](auto *request) { + if constexpr (QtPrivate::OperationHasFd_v<decltype(*request)>) + fd = request->fd; + }); + if (fd == -1) { + qCDebug(lcQIORing, "Invalid cancel for non-existant fd"); + invokeCallback(*cancelRequest); + return RequestPrepResult::RequestCompleted; + } + // NOLINTNEXTLINE(performance-no-int-to-ptr) + const IORING_HANDLE_REF fileRef((HANDLE(fd))); + hr = BuildIoRingCancelRequest(ioRingHandle, fileRef, quintptr(otherOperation), + quintptr(std::addressof(request))); + break; + } + case Operation::NumOperations: + Q_UNREACHABLE_RETURN(RequestPrepResult::RequestCompleted); + break; + } + if (hr == IORING_E_SUBMISSION_QUEUE_FULL) + return RequestPrepResult::QueueFull; + if (FAILED(hr)) { + finishRequestWithError(request, QFileDevice::UnspecifiedError); + return RequestPrepResult::RequestCompleted; + } + request.setQueued(true); + return RequestPrepResult::Ok; +} + +bool QIORing::verifyFd(GenericRequestType &req) +{ + bool result = true; + invokeOnOp(req, [&](auto *request) { + if constexpr (QtPrivate::OperationHasFd_v<decltype(*request)>) { + result = quintptr(request->fd) > 0 && quintptr(request->fd) != quintptr(INVALID_HANDLE_VALUE); + } + }); + return result; +} + +void QIORing::GenericRequestType::cleanupExtra(Operation op, void *extra) +{ + switch (op) { + case QtPrivate::Operation::Read: + case QtPrivate::Operation::VectoredRead: + case QtPrivate::Operation::Write: + case QtPrivate::Operation::VectoredWrite: + delete static_cast<QtPrivate::ReadWriteExtra *>(extra); + break; + case QtPrivate::Operation::Open: + case QtPrivate::Operation::Close: + case QtPrivate::Operation::Flush: + case QtPrivate::Operation::Stat: + case QtPrivate::Operation::Cancel: + case QtPrivate::Operation::NumOperations: + break; + } +} + +QT_END_NAMESPACE diff --git a/src/corelib/io/qrandomaccessasyncfile_darwin.mm b/src/corelib/io/qrandomaccessasyncfile_darwin.mm index 7231d12fe7d..fca225263ba 100644 --- a/src/corelib/io/qrandomaccessasyncfile_darwin.mm +++ b/src/corelib/io/qrandomaccessasyncfile_darwin.mm @@ -95,10 +95,8 @@ void QRandomAccessAsyncFilePrivate::close() // cancel all operations m_mutex.lock(); m_opToCancel = kAllOperationIds; - m_numChannelsToClose = m_ioChannel ? 1 : 0; for (const auto &op : m_operations) { if (op.channel) { - ++m_numChannelsToClose; closeIoChannel(op.channel); } } @@ -212,8 +210,8 @@ QRandomAccessAsyncFilePrivate::writeFrom(qint64 offset, QSpan<const QSpan<const void QRandomAccessAsyncFilePrivate::notifyIfOperationsAreCompleted() { QMutexLocker locker(&m_mutex); + --m_numChannelsToClose; if (m_opToCancel == kAllOperationIds) { - --m_numChannelsToClose; if (m_numChannelsToClose == 0 && m_runningOps.isEmpty()) m_cancellationCondition.wakeOne(); } @@ -222,11 +220,19 @@ void QRandomAccessAsyncFilePrivate::notifyIfOperationsAreCompleted() dispatch_io_t QRandomAccessAsyncFilePrivate::createMainChannel(int fd) { auto sharedThis = this; - return dispatch_io_create(DISPATCH_IO_RANDOM, fd, - dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), - ^(int /*error*/) { - sharedThis->notifyIfOperationsAreCompleted(); - }); + auto channel = + dispatch_io_create(DISPATCH_IO_RANDOM, fd, + dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), + ^(int /*error*/) { + // main I/O channel uses kInvalidOperationId + // as its identifier + sharedThis->notifyIfOperationsAreCompleted(); + }); + if (channel) { + QMutexLocker locker(&m_mutex); + ++m_numChannelsToClose; + } + return channel; } dispatch_io_t QRandomAccessAsyncFilePrivate::duplicateIoChannel(OperationId opId) @@ -247,6 +253,7 @@ dispatch_io_t QRandomAccessAsyncFilePrivate::duplicateIoChannel(OperationId opId if (channel) { QMutexLocker locker(&m_mutex); m_runningOps.insert(opId); + ++m_numChannelsToClose; } return channel; } @@ -593,6 +600,7 @@ void QRandomAccessAsyncFilePrivate::executeOpen(OperationInfo &opInfo) // So we need to notify the condition variable in // both cases. Q_ASSERT(sharedThis->m_runningOps.isEmpty()); + Q_ASSERT(sharedThis->m_numChannelsToClose == 0); sharedThis->m_cancellationCondition.wakeOne(); } else { auto context = sharedThis->q_ptr; diff --git a/src/corelib/io/qrandomaccessasyncfile_p_p.h b/src/corelib/io/qrandomaccessasyncfile_p_p.h index 924c9f9ed83..11ad788c884 100644 --- a/src/corelib/io/qrandomaccessasyncfile_p_p.h +++ b/src/corelib/io/qrandomaccessasyncfile_p_p.h @@ -43,6 +43,11 @@ #endif // Q_OS_DARWIN +#ifdef QT_RANDOMACCESSASYNCFILE_QIORING +#include <QtCore/private/qioring_p.h> +#include <QtCore/qlist.h> +#endif + QT_BEGIN_NAMESPACE class QRandomAccessAsyncFilePrivate : public QObjectPrivate @@ -114,6 +119,15 @@ private: void processFlush(); void processOpen(); void operationComplete(); +#elif defined(QT_RANDOMACCESSASYNCFILE_QIORING) + void queueCompletion(QIOOperationPrivate *priv, QIOOperation::Error error); + void startReadIntoSingle(QIOOperation *op, const QSpan<std::byte> &to); + void startWriteFromSingle(QIOOperation *op, const QSpan<const std::byte> &from); + QIORing::RequestHandle cancel(QIORing::RequestHandle handle); + QIORing *m_ioring = nullptr; + qintptr m_fd = -1; + QList<QPointer<QIOOperation>> m_operations; + QHash<QIOOperation *, QIORing::RequestHandle> m_opHandleMap; #endif #ifdef Q_OS_DARWIN using OperationId = quint64; diff --git a/src/corelib/io/qrandomaccessasyncfile_qioring.cpp b/src/corelib/io/qrandomaccessasyncfile_qioring.cpp new file mode 100644 index 00000000000..c9783ea2856 --- /dev/null +++ b/src/corelib/io/qrandomaccessasyncfile_qioring.cpp @@ -0,0 +1,438 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default + +#include "qrandomaccessasyncfile_p_p.h" + +#include "qiooperation_p.h" +#include "qiooperation_p_p.h" + +#include <QtCore/qfile.h> // QtPrivate::toFilesystemPath +#include <QtCore/qtypes.h> +#include <QtCore/private/qioring_p.h> + +#include <QtCore/q26numeric.h> + +QT_BEGIN_NAMESPACE + +Q_STATIC_LOGGING_CATEGORY(lcQRandomAccessIORing, "qt.core.qrandomaccessasyncfile.ioring", + QtCriticalMsg); + +QRandomAccessAsyncFilePrivate::QRandomAccessAsyncFilePrivate() = default; + +QRandomAccessAsyncFilePrivate::~QRandomAccessAsyncFilePrivate() = default; + +void QRandomAccessAsyncFilePrivate::init() +{ + m_ioring = QIORing::sharedInstance(); + if (!m_ioring) + qCCritical(lcQRandomAccessIORing, "QRandomAccessAsyncFile: ioring failed to initialize"); +} + +QIORing::RequestHandle QRandomAccessAsyncFilePrivate::cancel(QIORing::RequestHandle handle) +{ + if (handle) { + QIORingRequest<QIORing::Operation::Cancel> cancelRequest; + cancelRequest.handle = handle; + return m_ioring->queueRequest(std::move(cancelRequest)); + } + return nullptr; +} + +void QRandomAccessAsyncFilePrivate::cancelAndWait(QIOOperation *op) +{ + auto *opHandle = m_opHandleMap.value(op); + if (auto *handle = cancel(opHandle)) { + m_ioring->waitForRequest(handle); + m_ioring->waitForRequest(opHandle); + } +} + +void QRandomAccessAsyncFilePrivate::queueCompletion(QIOOperationPrivate *priv, QIOOperation::Error error) +{ + // Remove the handle now in case the user cancels or deletes the io-operation + // before operationComplete is called - the null-handle will protect from + // nasty issues that may occur when trying to cancel an operation that's no + // longer in the queue: + m_opHandleMap.remove(priv->q_func()); + // @todo: Look into making it emit only if synchronously completed + QMetaObject::invokeMethod(priv->q_ptr, [priv, error](){ + priv->operationComplete(error); + }, Qt::QueuedConnection); +} + +QIOOperation *QRandomAccessAsyncFilePrivate::open(const QString &path, QIODeviceBase::OpenMode mode) +{ + auto *dataStorage = new QtPrivate::QIOOperationDataStorage(); + + auto *priv = new QIOOperationPrivate(dataStorage); + priv->type = QIOOperation::Type::Open; + + auto *op = new QIOOperation(*priv, q_ptr); + if (m_fileState != FileState::Closed) { + queueCompletion(priv, QIOOperation::Error::Open); + return op; + } + m_operations.append(op); + m_fileState = FileState::OpenPending; + + QIORingRequest<QIORing::Operation::Open> openOperation; + openOperation.path = QtPrivate::toFilesystemPath(path); + openOperation.flags = mode; + openOperation.setCallback([this, op, + priv](const QIORingRequest<QIORing::Operation::Open> &request) { + if (const auto *err = std::get_if<QFileDevice::FileError>(&request.result)) { + if (m_fileState != FileState::Opened) { + // We assume there was only one pending open() in flight. + m_fd = -1; + m_fileState = FileState::Closed; + } + if (priv->error == QIOOperation::Error::Aborted || *err == QFileDevice::AbortError) + queueCompletion(priv, QIOOperation::Error::Aborted); + else + queueCompletion(priv, QIOOperation::Error::Open); + } else if (const auto *result = std::get_if<QIORingResult<QIORing::Operation::Open>>( + &request.result)) { + if (m_fileState == FileState::OpenPending) { + m_fileState = FileState::Opened; + m_fd = result->fd; + queueCompletion(priv, QIOOperation::Error::None); + } else { // Something went wrong, we did not expect a callback: + // So we close the new handle: + QIORingRequest<QIORing::Operation::Close> closeRequest; + closeRequest.fd = result->fd; + QIORing::RequestHandle handle = m_ioring->queueRequest(std::move(closeRequest)); + // Since the user issued multiple open() calls they get to wait for the close() to + // finish: + m_ioring->waitForRequest(handle); + queueCompletion(priv, QIOOperation::Error::Open); + } + } + m_operations.removeOne(op); + }); + m_opHandleMap.insert(priv->q_func(), m_ioring->queueRequest(std::move(openOperation))); + + return op; +} + +void QRandomAccessAsyncFilePrivate::close() +{ + // all the operations should be aborted + const auto ops = std::exchange(m_operations, {}); + QList<QIORing::RequestHandle> tasksToAwait; + // Request to cancel all of the in-flight operations: + for (const auto &op : ops) { + if (op) { + op->d_func()->error = QIOOperation::Error::Aborted; + if (auto *opHandle = m_opHandleMap.value(op)) { + tasksToAwait.append(cancel(opHandle)); + tasksToAwait.append(opHandle); + } + } + } + + QIORingRequest<QIORing::Operation::Close> closeRequest; + closeRequest.fd = m_fd; + tasksToAwait.append(m_ioring->queueRequest(std::move(closeRequest))); + + // Wait for completion: + for (const QIORing::RequestHandle &handle : tasksToAwait) + m_ioring->waitForRequest(handle); + m_fileState = FileState::Closed; + m_fd = -1; +} + +qint64 QRandomAccessAsyncFilePrivate::size() const +{ + QIORingRequest<QIORing::Operation::Stat> statRequest; + statRequest.fd = m_fd; + qint64 finalSize = 0; + statRequest.setCallback([&finalSize](const QIORingRequest<QIORing::Operation::Stat> &request) { + if (const auto *err = std::get_if<QFileDevice::FileError>(&request.result)) { + Q_UNUSED(err); + finalSize = -1; + } else if (const auto *res = std::get_if<QIORingResult<QIORing::Operation::Stat>>(&request.result)) { + finalSize = q26::saturate_cast<qint64>(res->size); + } + }); + auto *handle = m_ioring->queueRequest(std::move(statRequest)); + m_ioring->waitForRequest(handle); + + return finalSize; +} + +QIOOperation *QRandomAccessAsyncFilePrivate::flush() +{ + auto *dataStorage = new QtPrivate::QIOOperationDataStorage(); + + auto *priv = new QIOOperationPrivate(dataStorage); + priv->type = QIOOperation::Type::Flush; + + auto *op = new QIOOperation(*priv, q_ptr); + m_operations.append(op); + + QIORingRequest<QIORing::Operation::Flush> flushRequest; + flushRequest.fd = m_fd; + flushRequest.setCallback([this, op](const QIORingRequest<QIORing::Operation::Flush> &request) { + auto *priv = QIOOperationPrivate::get(op); + if (const auto *err = std::get_if<QFileDevice::FileError>(&request.result)) { + if (priv->error == QIOOperation::Error::Aborted || *err == QFileDevice::AbortError) + queueCompletion(priv, QIOOperation::Error::Aborted); + else if (*err == QFileDevice::OpenError) + queueCompletion(priv, QIOOperation::Error::FileNotOpen); + else + queueCompletion(priv, QIOOperation::Error::Flush); + } else if (std::get_if<QIORingResult<QIORing::Operation::Flush>>(&request.result)) { + queueCompletion(priv, QIOOperation::Error::None); + } + m_operations.removeOne(op); + }); + m_opHandleMap.insert(priv->q_func(), m_ioring->queueRequest(std::move(flushRequest))); + + return op; +} + +void QRandomAccessAsyncFilePrivate::startReadIntoSingle(QIOOperation *op, + const QSpan<std::byte> &to) +{ + QIORingRequest<QIORing::Operation::Read> readRequest; + readRequest.fd = m_fd; + auto *priv = QIOOperationPrivate::get(op); + if (priv->offset < 0) { // The QIORing offset is unsigned, so error out now + queueCompletion(priv, QIOOperation::Error::IncorrectOffset); + m_operations.removeOne(op); + return; + } + readRequest.offset = priv->offset; + readRequest.destination = to; + readRequest.setCallback([this, op](const QIORingRequest<QIORing::Operation::Read> &request) { + auto *priv = QIOOperationPrivate::get(op); + if (const auto *err = std::get_if<QFileDevice::FileError>(&request.result)) { + if (priv->error == QIOOperation::Error::Aborted || *err == QFileDevice::AbortError) + queueCompletion(priv, QIOOperation::Error::Aborted); + else if (*err == QFileDevice::OpenError) + queueCompletion(priv, QIOOperation::Error::FileNotOpen); + else if (*err == QFileDevice::PositionError) + queueCompletion(priv, QIOOperation::Error::IncorrectOffset); + else + queueCompletion(priv, QIOOperation::Error::Read); + } else if (const auto *result = std::get_if<QIORingResult<QIORing::Operation::Read>>( + &request.result)) { + priv->appendBytesProcessed(result->bytesRead); + if (priv->dataStorage->containsReadSpans()) + priv->dataStorage->getReadSpans().first().slice(0, result->bytesRead); + else + priv->dataStorage->getByteArray().slice(0, result->bytesRead); + + queueCompletion(priv, QIOOperation::Error::None); + } + m_operations.removeOne(op); + }); + m_opHandleMap.insert(priv->q_func(), m_ioring->queueRequest(std::move(readRequest))); +} + +QIOReadOperation *QRandomAccessAsyncFilePrivate::read(qint64 offset, qint64 maxSize) +{ + QByteArray array; + array.resizeForOverwrite(maxSize); + auto *dataStorage = new QtPrivate::QIOOperationDataStorage(std::move(array)); + + auto *priv = new QIOOperationPrivate(dataStorage); + priv->offset = offset; + priv->type = QIOOperation::Type::Read; + + auto *op = new QIOReadOperation(*priv, q_ptr); + m_operations.append(op); + + startReadIntoSingle(op, as_writable_bytes(QSpan(dataStorage->getByteArray()))); + + return op; +} + +QIOWriteOperation *QRandomAccessAsyncFilePrivate::write(qint64 offset, const QByteArray &data) +{ + return write(offset, QByteArray(data)); +} + +void QRandomAccessAsyncFilePrivate::startWriteFromSingle(QIOOperation *op, + const QSpan<const std::byte> &from) +{ + QIORingRequest<QIORing::Operation::Write> writeRequest; + writeRequest.fd = m_fd; + auto *priv = QIOOperationPrivate::get(op); + if (priv->offset < 0) { // The QIORing offset is unsigned, so error out now + queueCompletion(priv, QIOOperation::Error::IncorrectOffset); + m_operations.removeOne(op); + return; + } + writeRequest.offset = priv->offset; + writeRequest.source = from; + writeRequest.setCallback([this, op](const QIORingRequest<QIORing::Operation::Write> &request) { + auto *priv = QIOOperationPrivate::get(op); + if (const auto *err = std::get_if<QFileDevice::FileError>(&request.result)) { + if (priv->error == QIOOperation::Error::Aborted || *err == QFileDevice::AbortError) + queueCompletion(priv, QIOOperation::Error::Aborted); + else if (*err == QFileDevice::OpenError) + queueCompletion(priv, QIOOperation::Error::FileNotOpen); + else if (*err == QFileDevice::PositionError) + queueCompletion(priv, QIOOperation::Error::IncorrectOffset); + else + queueCompletion(priv, QIOOperation::Error::Write); + } else if (const auto *result = std::get_if<QIORingResult<QIORing::Operation::Write>>( + &request.result)) { + priv->appendBytesProcessed(result->bytesWritten); + queueCompletion(priv, QIOOperation::Error::None); + } + m_operations.removeOne(op); + }); + m_opHandleMap.insert(priv->q_func(), m_ioring->queueRequest(std::move(writeRequest))); +} + +QIOWriteOperation *QRandomAccessAsyncFilePrivate::write(qint64 offset, QByteArray &&data) +{ + auto *dataStorage = new QtPrivate::QIOOperationDataStorage(std::move(data)); + + auto *priv = new QIOOperationPrivate(dataStorage); + priv->offset = offset; + priv->type = QIOOperation::Type::Write; + + auto *op = new QIOWriteOperation(*priv, q_ptr); + m_operations.append(op); + + startWriteFromSingle(op, as_bytes(QSpan(dataStorage->getByteArray()))); + + return op; +} + +QIOVectoredReadOperation *QRandomAccessAsyncFilePrivate::readInto(qint64 offset, + QSpan<std::byte> buffer) +{ + auto *dataStorage = new QtPrivate::QIOOperationDataStorage( + QSpan<const QSpan<std::byte>>{ buffer }); + + auto *priv = new QIOOperationPrivate(dataStorage); + priv->offset = offset; + priv->type = QIOOperation::Type::Read; + + auto *op = new QIOVectoredReadOperation(*priv, q_ptr); + m_operations.append(op); + + startReadIntoSingle(op, dataStorage->getReadSpans().first()); + + return op; +} + +QIOVectoredWriteOperation *QRandomAccessAsyncFilePrivate::writeFrom(qint64 offset, + QSpan<const std::byte> buffer) +{ + auto *dataStorage = new QtPrivate::QIOOperationDataStorage( + QSpan<const QSpan<const std::byte>>{ buffer }); + + auto *priv = new QIOOperationPrivate(dataStorage); + priv->offset = offset; + priv->type = QIOOperation::Type::Write; + + auto *op = new QIOVectoredWriteOperation(*priv, q_ptr); + m_operations.append(op); + + startWriteFromSingle(op, dataStorage->getWriteSpans().first()); + + return op; +} + +QIOVectoredReadOperation * +QRandomAccessAsyncFilePrivate::readInto(qint64 offset, QSpan<const QSpan<std::byte>> buffers) +{ + if (!QIORing::supportsOperation(QtPrivate::Operation::VectoredRead)) + return nullptr; + auto *dataStorage = new QtPrivate::QIOOperationDataStorage(buffers); + + auto *priv = new QIOOperationPrivate(dataStorage); + priv->offset = offset; + priv->type = QIOOperation::Type::Read; + + auto *op = new QIOVectoredReadOperation(*priv, q_ptr); + if (priv->offset < 0) { // The QIORing offset is unsigned, so error out now + queueCompletion(priv, QIOOperation::Error::IncorrectOffset); + return op; + } + m_operations.append(op); + + QIORingRequest<QIORing::Operation::VectoredRead> readRequest; + readRequest.fd = m_fd; + readRequest.offset = priv->offset; + readRequest.destinations = dataStorage->getReadSpans(); + readRequest.setCallback([this, + op](const QIORingRequest<QIORing::Operation::VectoredRead> &request) { + auto *priv = QIOOperationPrivate::get(op); + if (const auto *err = std::get_if<QFileDevice::FileError>(&request.result)) { + if (priv->error == QIOOperation::Error::Aborted || *err == QFileDevice::AbortError) + queueCompletion(priv, QIOOperation::Error::Aborted); + else + queueCompletion(priv, QIOOperation::Error::Read); + } else if (const auto + *result = std::get_if<QIORingResult<QIORing::Operation::VectoredRead>>( + &request.result)) { + priv->appendBytesProcessed(result->bytesRead); + qint64 processed = result->bytesRead; + for (auto &span : priv->dataStorage->getReadSpans()) { + if (span.size() < processed) { + processed -= span.size(); + } else { // span.size >= processed + span.slice(0, processed); + processed = 0; + } + } + queueCompletion(priv, QIOOperation::Error::None); + } + m_operations.removeOne(op); + }); + m_opHandleMap.insert(priv->q_func(), m_ioring->queueRequest(std::move(readRequest))); + + return op; +} + +QIOVectoredWriteOperation * +QRandomAccessAsyncFilePrivate::writeFrom(qint64 offset, QSpan<const QSpan<const std::byte>> buffers) +{ + if (!QIORing::supportsOperation(QtPrivate::Operation::VectoredWrite)) + return nullptr; + auto *dataStorage = new QtPrivate::QIOOperationDataStorage(buffers); + + auto *priv = new QIOOperationPrivate(dataStorage); + priv->offset = offset; + priv->type = QIOOperation::Type::Write; + + auto *op = new QIOVectoredWriteOperation(*priv, q_ptr); + if (priv->offset < 0) { // The QIORing offset is unsigned, so error out now + queueCompletion(priv, QIOOperation::Error::IncorrectOffset); + return op; + } + m_operations.append(op); + + QIORingRequest<QIORing::Operation::VectoredWrite> writeRequest; + writeRequest.fd = m_fd; + writeRequest.offset = priv->offset; + writeRequest.sources = dataStorage->getWriteSpans(); + writeRequest.setCallback( + [this, op](const QIORingRequest<QIORing::Operation::VectoredWrite> &request) { + auto *priv = QIOOperationPrivate::get(op); + if (const auto *err = std::get_if<QFileDevice::FileError>(&request.result)) { + if (priv->error == QIOOperation::Error::Aborted || *err == QFileDevice::AbortError) + queueCompletion(priv, QIOOperation::Error::Aborted); + else + queueCompletion(priv, QIOOperation::Error::Write); + } else if (const auto *result = std::get_if< + QIORingResult<QIORing::Operation::VectoredWrite>>( + &request.result)) { + priv->appendBytesProcessed(result->bytesWritten); + queueCompletion(priv, QIOOperation::Error::None); + } + m_operations.removeOne(op); + }); + m_opHandleMap.insert(priv->q_func(), m_ioring->queueRequest(std::move(writeRequest))); + + return op; +} + +QT_END_NAMESPACE diff --git a/src/corelib/io/qwindowspipereader.cpp b/src/corelib/io/qwindowspipereader.cpp index 456a209af37..6edf4395887 100644 --- a/src/corelib/io/qwindowspipereader.cpp +++ b/src/corelib/io/qwindowspipereader.cpp @@ -14,6 +14,11 @@ using namespace Qt::StringLiterals; static const DWORD minReadBufferSize = 4096; +/*! + \class QWindowsPipeReader + \inmodule QtCore + \internal +*/ QWindowsPipeReader::QWindowsPipeReader(QObject *parent) : QObject(parent), handle(INVALID_HANDLE_VALUE), diff --git a/src/corelib/itemmodels/qrangemodel.cpp b/src/corelib/itemmodels/qrangemodel.cpp index f533605c721..f37812876ea 100644 --- a/src/corelib/itemmodels/qrangemodel.cpp +++ b/src/corelib/itemmodels/qrangemodel.cpp @@ -154,7 +154,7 @@ static bool connectPropertiesHelper(const QModelIndex &index, QObject *item, QOb const QHash<int, QMetaProperty> &properties) { if (!item) - return false; + return true; for (auto &&[role, property] : properties.asKeyValueRange()) { if (property.hasNotifySignal()) { if (!Handler(index, item, context, role, property)) @@ -171,7 +171,7 @@ bool QRangeModelImplBase::connectProperty(const QModelIndex &index, QObject *ite int role, const QMetaProperty &property) { if (!item) - return false; + return true; // nothing to do, continue PropertyChangedHandler handler{index, role}; auto connection = property.enclosingMetaObject()->connect(item, property.notifySignal(), context, std::move(handler)); @@ -199,7 +199,7 @@ bool QRangeModelImplBase::connectPropertyConst(const QModelIndex &index, QObject int role, const QMetaProperty &property) { if (!item) - return false; + return true; // nothing to do, continue ConstPropertyChangedHandler handler{index, role}; if (!property.enclosingMetaObject()->connect(item, property.notifySignal(), context, std::move(handler))) { @@ -216,6 +216,27 @@ bool QRangeModelImplBase::connectPropertiesConst(const QModelIndex &index, QObje return connectPropertiesHelper<QRangeModelImplBase::connectPropertyConst>(index, item, context, properties); } +namespace QRangeModelDetails +{ +Q_CORE_EXPORT QVariant qVariantAtIndex(const QModelIndex &index) +{ + QModelRoleData result[] = { + QModelRoleData{Qt::RangeModelAdapterRole}, + QModelRoleData{Qt::RangeModelDataRole}, + QModelRoleData{Qt::DisplayRole}, + }; + index.multiData(result); + QVariant variant; + size_t r = 0; + do { + variant = result[r].data(); + ++r; + } while (!variant.isValid() && r < std::size(result)); + + return variant; +} +} + /*! \class QRangeModel \inmodule QtCore @@ -1364,6 +1385,7 @@ void QRangeModel::resetRoleNames() /*! \property QRangeModel::autoConnectPolicy + \since 6.11 \brief if and when the model auto-connects to property changed notifications. If QRangeModel operates on a data structure that holds the same type of diff --git a/src/corelib/itemmodels/qrangemodel.h b/src/corelib/itemmodels/qrangemodel.h index d15f40d37a9..b8500f9ae94 100644 --- a/src/corelib/itemmodels/qrangemodel.h +++ b/src/corelib/itemmodels/qrangemodel.h @@ -150,6 +150,14 @@ void QRangeModelImplBase::dataChanged(const QModelIndex &from, const QModelIndex { m_rangeModel->dataChanged(from, to, roles); } +void QRangeModelImplBase::beginResetModel() +{ + m_rangeModel->beginResetModel(); +} +void QRangeModelImplBase::endResetModel() +{ + m_rangeModel->endResetModel(); +} void QRangeModelImplBase::beginInsertColumns(const QModelIndex &parent, int start, int count) { m_rangeModel->beginInsertColumns(parent, start, count); diff --git a/src/corelib/itemmodels/qrangemodel_impl.h b/src/corelib/itemmodels/qrangemodel_impl.h index 70552cbfe05..7eca3094a66 100644 --- a/src/corelib/itemmodels/qrangemodel_impl.h +++ b/src/corelib/itemmodels/qrangemodel_impl.h @@ -23,12 +23,13 @@ #include <QtCore/qmap.h> #include <QtCore/qscopedvaluerollback.h> #include <QtCore/qset.h> +#include <QtCore/qvarlengtharray.h> #include <algorithm> #include <functional> #include <iterator> #include <type_traits> -#include <QtCore/q20type_traits.h> +#include <QtCore/qxptype_traits.h> #include <tuple> #include <QtCore/q23utility.h> @@ -109,14 +110,17 @@ namespace QRangeModelDetails using wrapped_t = std::remove_pointer_t<decltype(pointerTo(std::declval<T&>()))>; template <typename T> - using is_wrapped = std::negation<std::is_same<wrapped_t<T>, std::remove_reference_t<T>>>; + using is_wrapped = std::negation<std::is_same< + QRangeModelDetails::wrapped_t<T>, std::remove_reference_t<T> + >>; template <typename T, typename = void> struct tuple_like : std::false_type {}; template <typename T, std::size_t N> struct tuple_like<std::array<T, N>> : std::false_type {}; template <typename T> - struct tuple_like<T, std::void_t<std::tuple_element_t<0, wrapped_t<T>>>> : std::true_type {}; + struct tuple_like<T, std::void_t<std::tuple_element_t<0, QRangeModelDetails::wrapped_t<T>>>> + : std::true_type {}; template <typename T> [[maybe_unused]] static constexpr bool tuple_like_v = tuple_like<T>::value; @@ -132,7 +136,7 @@ namespace QRangeModelDetails template <typename T, typename = void> struct has_metaobject : std::false_type {}; template <typename T> - struct has_metaobject<T, std::void_t<decltype(wrapped_t<T>::staticMetaObject)>> + struct has_metaobject<T, std::void_t<decltype(QRangeModelDetails::wrapped_t<T>::staticMetaObject)>> : std::true_type {}; template <typename T> [[maybe_unused]] static constexpr bool has_metaobject_v = has_metaobject<T>::value; @@ -235,15 +239,15 @@ namespace QRangeModelDetails : std::true_type {}; - // we use std::rotate in moveRows/Columns, which requires std::swap - template <typename It, typename = void> - struct test_rotate : std::false_type {}; - + // we use std::rotate in moveRows/Columns, which requires the values (which + // might be const if we only get a const iterator) to be swappable, and the + // iterator type to be at least a forward iterator template <typename It> - struct test_rotate<It, std::void_t<decltype(std::swap(*std::declval<It>(), - *std::declval<It>()))>> - : std::true_type - {}; + using test_rotate = std::conjunction< + std::is_swappable<decltype(*std::declval<It>())>, + std::is_base_of<std::forward_iterator_tag, + typename std::iterator_traits<It>::iterator_category> + >; template <typename C, typename = void> struct test_splice : std::false_type {}; @@ -492,12 +496,12 @@ namespace QRangeModelDetails template <typename T> [[maybe_unused]] static constexpr int static_size_v = - row_traits<std::remove_cv_t<wrapped_t<T>>>::static_size; + row_traits<std::remove_cv_t<QRangeModelDetails::wrapped_t<T>>>::static_size; template <typename Range> struct ListProtocol { - using row_type = typename range_traits<wrapped_t<Range>>::value_type; + using row_type = typename range_traits<QRangeModelDetails::wrapped_t<Range>>::value_type; template <typename R = row_type> auto newRow() -> decltype(R{}) { return R{}; } @@ -506,16 +510,20 @@ namespace QRangeModelDetails template <typename Range> struct TableProtocol { - using row_type = typename range_traits<wrapped_t<Range>>::value_type; + using row_type = typename range_traits<QRangeModelDetails::wrapped_t<Range>>::value_type; template <typename R = row_type, - std::enable_if_t<std::conjunction_v<std::is_destructible<wrapped_t<R>>, - is_owning_or_raw_pointer<R>>, bool> = true> - auto newRow() -> decltype(R(new wrapped_t<R>)) { + std::enable_if_t< + std::conjunction_v< + std::is_destructible<QRangeModelDetails::wrapped_t<R>>, + is_owning_or_raw_pointer<R> + >, + bool> = true> + auto newRow() -> decltype(R(new QRangeModelDetails::wrapped_t<R>)) { if constexpr (is_any_of<R, std::shared_ptr>()) - return std::make_shared<wrapped_t<R>>(); + return std::make_shared<QRangeModelDetails::wrapped_t<R>>(); else - return R(new wrapped_t<R>); + return R(new QRangeModelDetails::wrapped_t<R>); } template <typename R = row_type, @@ -527,7 +535,8 @@ namespace QRangeModelDetails auto deleteRow(R&& row) -> decltype(delete row) { delete row; } }; - template <typename Range, typename R = typename range_traits<wrapped_t<Range>>::value_type> + template <typename Range, + typename R = typename range_traits<QRangeModelDetails::wrapped_t<Range>>::value_type> using table_protocol_t = std::conditional_t<static_size_v<R> == 0 && !has_metaobject_v<R>, ListProtocol<Range>, TableProtocol<Range>>; @@ -562,35 +571,30 @@ namespace QRangeModelDetails } }; - template <typename P, typename R, typename = void> - struct protocol_parentRow : std::false_type {}; template <typename P, typename R> - struct protocol_parentRow<P, R, - std::void_t<decltype(std::declval<P&>().parentRow(std::declval<wrapped_t<R>&>()))>> - : std::true_type {}; + using protocol_parentRow_test = decltype(std::declval<P&>() + .parentRow(std::declval<QRangeModelDetails::wrapped_t<R>&>())); + template <typename P, typename R> + using protocol_parentRow = qxp::is_detected<protocol_parentRow_test, P, R>; - template <typename P, typename R, typename = void> - struct protocol_childRows : std::false_type {}; template <typename P, typename R> - struct protocol_childRows<P, R, - std::void_t<decltype(std::declval<P&>().childRows(std::declval<wrapped_t<R>&>()))>> - : std::true_type {}; + using protocol_childRows_test = decltype(std::declval<P&>() + .childRows(std::declval<QRangeModelDetails::wrapped_t<R>&>())); + template <typename P, typename R> + using protocol_childRows = qxp::is_detected<protocol_childRows_test, P, R>; - template <typename P, typename R, typename = void> - struct protocol_setParentRow : std::false_type {}; template <typename P, typename R> - struct protocol_setParentRow<P, R, - std::void_t<decltype(std::declval<P&>().setParentRow(std::declval<wrapped_t<R>&>(), - std::declval<wrapped_t<R>*>()))>> - : std::true_type {}; + using protocol_setParentRow_test = decltype(std::declval<P&>() + .setParentRow(std::declval<QRangeModelDetails::wrapped_t<R>&>(), + std::declval<QRangeModelDetails::wrapped_t<R>*>())); + template <typename P, typename R> + using protocol_setParentRow = qxp::is_detected<protocol_setParentRow_test, P, R>; - template <typename P, typename R, typename = void> - struct protocol_mutable_childRows : std::false_type {}; template <typename P, typename R> - struct protocol_mutable_childRows<P, R, - std::void_t<decltype(refTo(std::declval<P&>().childRows(std::declval<wrapped_t<R>&>())) - = {}) >> - : std::true_type {}; + using protocol_mutable_childRows_test = decltype(refTo(std::declval<P&>() + .childRows(std::declval<QRangeModelDetails::wrapped_t<R>&>())) = {}); + template <typename P, typename R> + using protocol_mutable_childRows = qxp::is_detected<protocol_mutable_childRows_test, P, R>; template <typename P, typename = void> struct protocol_newRow : std::false_type {}; @@ -618,20 +622,23 @@ namespace QRangeModelDetails > : std::true_type {}; template <typename Range> - using if_table_range = std::enable_if_t<std::conjunction_v<is_range<wrapped_t<Range>>, - std::negation<is_tree_range<wrapped_t<Range>>>>, - bool>; + using if_table_range = std::enable_if_t<std::conjunction_v< + is_range<QRangeModelDetails::wrapped_t<Range>>, + std::negation<is_tree_range<QRangeModelDetails::wrapped_t<Range>>> + >, bool>; template <typename Range, typename Protocol = DefaultTreeProtocol<Range>> - using if_tree_range = std::enable_if_t<std::conjunction_v<is_range<wrapped_t<Range>>, - is_tree_range<wrapped_t<Range>, wrapped_t<Protocol>>>, - bool>; + using if_tree_range = std::enable_if_t<std::conjunction_v< + is_range<QRangeModelDetails::wrapped_t<Range>>, + is_tree_range<QRangeModelDetails::wrapped_t<Range>, + QRangeModelDetails::wrapped_t<Protocol>> + >, bool>; template <typename Range, typename Protocol> struct protocol_traits { - using protocol = wrapped_t<Protocol>; - using row = typename range_traits<wrapped_t<Range>>::value_type; + using protocol = QRangeModelDetails::wrapped_t<Protocol>; + using row = typename range_traits<QRangeModelDetails::wrapped_t<Range>>::value_type; static constexpr bool is_tree = std::conjunction_v<protocol_parentRow<protocol, row>, protocol_childRows<protocol, row>>; static constexpr bool is_list = static_size_v<row> == 0 @@ -718,7 +725,7 @@ namespace QRangeModelDetails PropertyData<has_metaobject_v<ItemType>, std::is_base_of_v<QObject, std::remove_pointer_t<ItemType>>> { - using WrappedStorage = Storage<wrapped_t<ModelStorage>>; + using WrappedStorage = Storage<QRangeModelDetails::wrapped_t<ModelStorage>>; using iterator = typename WrappedStorage::iterator; using const_iterator = typename WrappedStorage::const_iterator; @@ -733,6 +740,8 @@ namespace QRangeModelDetails } // namespace QRangeModelDetails class QRangeModel; +// forward declare so that we can declare friends +template <typename, typename, typename> class QRangeModelAdapter; class QRangeModelImplBase : public QtPrivate::QQuasiVirtualInterface<QRangeModelImplBase> { @@ -895,6 +904,8 @@ protected: inline void changePersistentIndexList(const QModelIndexList &from, const QModelIndexList &to); inline void dataChanged(const QModelIndex &from, const QModelIndex &to, const QList<int> &roles); + inline void beginResetModel(); + inline void endResetModel(); inline void beginInsertColumns(const QModelIndex &parent, int start, int count); inline void endInsertColumns(); inline void beginRemoveColumns(const QModelIndex &parent, int start, int count); @@ -990,8 +1001,8 @@ protected: using std::distance; #endif using container_type = std::conditional_t<range_traits<C>::has_cbegin, - const wrapped_t<C>, - wrapped_t<C>>; + const QRangeModelDetails::wrapped_t<C>, + QRangeModelDetails::wrapped_t<C>>; container_type& container = const_cast<container_type &>(refTo(c)); return int(distance(std::begin(container), std::end(container))); } @@ -1191,75 +1202,49 @@ public: return std::move(result.data()); } + static constexpr bool isRangeModelRole(int role) + { + return role == Qt::RangeModelDataRole + || role == Qt::RangeModelAdapterRole; + } + + static constexpr bool isPrimaryRole(int role) + { + return role == Qt::DisplayRole || role == Qt::EditRole; + } + QMap<int, QVariant> itemData(const QModelIndex &index) const { QMap<int, QVariant> result; - bool tried = false; - const auto readItemData = [this, &index, &result, &tried](const auto &value){ - Q_UNUSED(this); - Q_UNUSED(index); - using value_type = q20::remove_cvref_t<decltype(value)>; - using multi_role = QRangeModelDetails::is_multi_role<value_type>; - using wrapped_value_type = QRangeModelDetails::wrapped_t<value_type>; - if constexpr (QRangeModelDetails::item_access<wrapped_value_type>()) { - using ItemAccess = QRangeModelDetails::QRangeModelItemAccess<wrapped_value_type>; - tried = true; + if (index.isValid()) { + bool tried = false; + + // optimisation for items backed by a QMap<int, QVariant> or equivalent + readAt(index, [&result, &tried](const auto &value) { + if constexpr (std::is_convertible_v<decltype(value), decltype(result)>) { + tried = true; + result = value; + } + }); + if (!tried) { const auto roles = this->itemModel().roleNames().keys(); - for (auto &role : roles) { - if (role == Qt::RangeModelDataRole) + QVarLengthArray<QModelRoleData, 16> roleDataArray; + roleDataArray.reserve(roles.size()); + for (auto role : roles) { + if (isRangeModelRole(role)) continue; - QVariant data = ItemAccess::readRole(value, role); - if (data.isValid()) - result[role] = std::move(data); - } - } else if constexpr (multi_role()) { - tried = true; - if constexpr (std::is_convertible_v<value_type, decltype(result)>) { - result = value; - } else { - const auto roleNames = [this]() -> QHash<int, QByteArray> { - Q_UNUSED(this); - if constexpr (!multi_role::int_key) - return this->itemModel().roleNames(); - else - return {}; - }(); - for (auto it = std::begin(value); it != std::end(value); ++it) { - const int role = [&roleNames, key = QRangeModelDetails::key(it)]() { - Q_UNUSED(roleNames); - if constexpr (multi_role::int_key) - return int(key); - else - return roleNames.key(key.toUtf8(), -1); - }(); - - if (role != -1 && role != Qt::RangeModelDataRole) - result.insert(role, QRangeModelDetails::value(it)); - } + roleDataArray.emplace_back(role); } - } else if constexpr (has_metaobject<value_type>) { - if (row_traits::fixed_size() <= 1) { - tried = true; - const auto roleNames = this->itemModel().roleNames(); - const auto end = roleNames.keyEnd(); - for (auto it = roleNames.keyBegin(); it != end; ++it) { - const int role = *it; - if (role == Qt::RangeModelDataRole) - continue; - QVariant data = readRole(index, role, QRangeModelDetails::pointerTo(value)); - if (data.isValid()) - result[role] = std::move(data); - } + QModelRoleDataSpan roleDataSpan(roleDataArray); + multiData(index, roleDataSpan); + + for (auto &&roleData : std::move(roleDataSpan)) { + QVariant data = roleData.data(); + if (data.isValid()) + result[roleData.role()] = std::move(data); } } - }; - - if (index.isValid()) { - readAt(index, readItemData); - - if (!tried) // no multi-role item found - result = this->itemModel().QAbstractItemModel::itemData(index); } return result; } @@ -1274,21 +1259,34 @@ public: using multi_role = QRangeModelDetails::is_multi_role<value_type>; using wrapped_value_type = QRangeModelDetails::wrapped_t<value_type>; + const auto readModelData = [&value](QModelRoleData &roleData){ + const int role = roleData.role(); + if (role == Qt::RangeModelDataRole) { + // Qt QML support: "modelData" role returns the entire multi-role item. + // QML can only use raw pointers to QObject (so we unwrap), and gadgets + // only by value (so we take the reference). + if constexpr (std::is_copy_assignable_v<wrapped_value_type>) + roleData.setData(QVariant::fromValue(QRangeModelDetails::refTo(value))); + else + roleData.setData(QVariant::fromValue(QRangeModelDetails::pointerTo(value))); + } else if (role == Qt::RangeModelAdapterRole) { + // for QRangeModelAdapter however, we want to respect smart pointer wrappers + if constexpr (std::is_copy_assignable_v<value_type>) + roleData.setData(QVariant::fromValue(value)); + else + roleData.setData(QVariant::fromValue(QRangeModelDetails::pointerTo(value))); + } else { + return false; + } + return true; + }; + if constexpr (QRangeModelDetails::item_access<wrapped_value_type>()) { using ItemAccess = QRangeModelDetails::QRangeModelItemAccess<wrapped_value_type>; tried = true; for (auto &roleData : roleDataSpan) { - if (roleData.role() == Qt::RangeModelDataRole) { - // Qt QML support: "modelData" role returns the entire multi-role item. - // QML can only use raw pointers to QObject (so we unwrap), and gadgets - // only by value (so we take the reference). - if constexpr (std::is_copy_assignable_v<wrapped_value_type>) - roleData.setData(QVariant::fromValue(QRangeModelDetails::refTo(value))); - else - roleData.setData(QVariant::fromValue(QRangeModelDetails::pointerTo(value))); - } else { + if (!readModelData(roleData)) roleData.setData(ItemAccess::readRole(value, roleData.role())); - } } } else if constexpr (multi_role()) { tried = true; @@ -1317,15 +1315,7 @@ public: if (row_traits::fixed_size() <= 1) { tried = true; for (auto &roleData : roleDataSpan) { - if (roleData.role() == Qt::RangeModelDataRole) { - // Qt QML support: "modelData" role returns the entire multi-role item. - // QML can only use raw pointers to QObject (so we unwrap), and gadgets - // only by value (so we take the reference). - if constexpr (std::is_copy_assignable_v<wrapped_value_type>) - roleData.setData(QVariant::fromValue(QRangeModelDetails::refTo(value))); - else - roleData.setData(QVariant::fromValue(QRangeModelDetails::pointerTo(value))); - } else { + if (!readModelData(roleData)) { roleData.setData(readRole(index, roleData.role(), QRangeModelDetails::pointerTo(value))); } @@ -1334,7 +1324,7 @@ public: tried = true; for (auto &roleData : roleDataSpan) { const int role = roleData.role(); - if (role == Qt::DisplayRole || role == Qt::EditRole) { + if (isPrimaryRole(role)) { roleData.setData(readProperty(index.column(), QRangeModelDetails::pointerTo(value))); } else { @@ -1346,12 +1336,10 @@ public: tried = true; for (auto &roleData : roleDataSpan) { const int role = roleData.role(); - if (role == Qt::DisplayRole || role == Qt::EditRole - || role == Qt::RangeModelDataRole) { + if (isPrimaryRole(role) || isRangeModelRole(role)) roleData.setData(read(value)); - } else { + else roleData.clearData(); - } } } }); @@ -1370,6 +1358,7 @@ public: if (success) { Q_EMIT this->dataChanged(index, index, role == Qt::EditRole || role == Qt::RangeModelDataRole + || role == Qt::RangeModelAdapterRole ? QList<int>{} : QList<int>{role}); } }); @@ -1382,16 +1371,34 @@ public: using multi_role = QRangeModelDetails::is_multi_role<value_type>; auto setRangeModelDataRole = [&target, &data]{ - auto &targetRef = QRangeModelDetails::refTo(target); constexpr auto targetMetaType = QMetaType::fromType<value_type>(); const auto dataMetaType = data.metaType(); + constexpr bool isWrapped = QRangeModelDetails::is_wrapped<value_type>(); if constexpr (!std::is_copy_assignable_v<wrapped_value_type>) { - // This covers move-only types, but also polymorph types like QObject. - // We don't support replacing a stored object with another one, as this - // makes object ownership very messy. - // fall through to error handling - } else if constexpr (QRangeModelDetails::is_wrapped<value_type>()) { - if (QRangeModelDetails::isValid(targetRef)) { + // we don't support replacing objects that are stored as raw pointers, + // as this makes object ownership very messy. But we can replace objects + // stored in smart pointers, and we can initialize raw nullptr objects. + if constexpr (isWrapped) { + constexpr bool is_raw_pointer = std::is_pointer_v<value_type>; + if constexpr (!is_raw_pointer && std::is_copy_assignable_v<value_type>) { + if (data.canConvert(targetMetaType)) { + target = data.value<value_type>(); + return true; + } + } else if constexpr (is_raw_pointer) { + if (!QRangeModelDetails::isValid(target) && data.canConvert(targetMetaType)) { + target = data.value<value_type>(); + return true; + } + } else { + Q_UNUSED(target); + } + } + // Otherwise we have a move-only or polymorph type. fall through to + // error handling. + } else if constexpr (isWrapped) { + if (QRangeModelDetails::isValid(target)) { + auto &targetRef = QRangeModelDetails::refTo(target); // we need to get a wrapped value type out of the QVariant, which // might carry a pointer. We have to try all alternatives. if (const auto mt = QMetaType::fromType<wrapped_value_type>(); @@ -1405,10 +1412,10 @@ public: } } } else if (targetMetaType == dataMetaType) { - targetRef = data.value<value_type>(); + QRangeModelDetails::refTo(target) = data.value<value_type>(); return true; } else if (dataMetaType.flags() & QMetaType::PointerToGadget) { - targetRef = *data.value<value_type *>(); + QRangeModelDetails::refTo(target) = *data.value<value_type *>(); return true; } #ifndef QT_NO_DEBUG @@ -1420,16 +1427,16 @@ public: if constexpr (QRangeModelDetails::item_access<wrapped_value_type>()) { using ItemAccess = QRangeModelDetails::QRangeModelItemAccess<wrapped_value_type>; - if (role == Qt::RangeModelDataRole) + if (isRangeModelRole(role)) return setRangeModelDataRole(); return ItemAccess::writeRole(target, data, role); } if constexpr (has_metaobject<value_type>) { if (row_traits::fixed_size() <= 1) { // multi-role value - if (role == Qt::RangeModelDataRole) + if (isRangeModelRole(role)) return setRangeModelDataRole(); return writeRole(role, QRangeModelDetails::pointerTo(target), data); } else if (column <= row_traits::fixed_size() // multi-column - && (role == Qt::DisplayRole || role == Qt::EditRole || role == Qt::RangeModelDataRole)) { + && (isPrimaryRole(role) || isRangeModelRole(role))) { return writeProperty(column, QRangeModelDetails::pointerTo(target), data); } } else if constexpr (multi_role::value) { @@ -1456,14 +1463,20 @@ public: return write(target[roleToSet], data); else return write(target[roleNames.value(roleToSet)], data); - } else if (role == Qt::DisplayRole || role == Qt::EditRole - || role == Qt::RangeModelDataRole) { + } else if (isPrimaryRole(role) || isRangeModelRole(role)) { return write(target, data); } return false; }; success = writeAt(index, writeData); + + if constexpr (itemsAreQObjects) { + if (success && isRangeModelRole(role) && this->autoConnectPolicy() == AutoConnectPolicy::Full) { + if (QObject *item = data.value<QObject *>()) + Self::connectProperties(index, item, m_data.context, m_data.properties); + } + } } return success; } @@ -1571,7 +1584,7 @@ public: tried = true; auto targetCopy = makeCopy(target); for (auto &&[role, value] : data.asKeyValueRange()) { - if (role == Qt::RangeModelDataRole) + if (isRangeModelRole(role)) continue; if (!writeRole(role, QRangeModelDetails::pointerTo(targetCopy), value)) { const QByteArray roleName = roleNames.value(role); @@ -1648,6 +1661,13 @@ public: return this->itemModel().QAbstractItemModel::roleNames(); } + template <typename Fn, std::size_t ...Is> + static bool forEachTupleElement(const row_type &row, Fn &&fn, std::index_sequence<Is...>) + { + using std::get; + return (std::forward<Fn>(fn)(QRangeModelDetails::pointerTo(get<Is>(row))) && ...); + } + template <typename Fn> bool forEachColumn(const row_type &row, int rowIndex, const QModelIndex &parent, Fn &&fn) const { @@ -1656,24 +1676,23 @@ public: const auto &model = this->itemModel(); if constexpr (one_dimensional_range) { return fn(model.index(rowIndex, 0, parent), pointerTo(row)); - } else if constexpr (dynamicColumns()) { + } else if constexpr (dynamicColumns() || QRangeModelDetails::array_like_v<row_type>) { int columnIndex = -1; return std::all_of(begin(row), end(row), [&](const auto &item) { return fn(model.index(rowIndex, ++columnIndex, parent), pointerTo(item)); }); - } else { // tuple-like - int columnIndex = -1; - return std::apply([fn = std::forward<Fn>(fn), &model, rowIndex, &columnIndex, parent] - (const auto &...item) { - return (fn(model.index(rowIndex, ++columnIndex, parent), pointerTo(item)) && ...); - }, row); + } else { // tuple-like (but not necessarily std::tuple, so can't use std::apply) + int column = -1; + return forEachTupleElement(row, [&column, &fn, &model, &rowIndex, &parent](QObject *item){ + return std::forward<Fn>(fn)(model.index(rowIndex, ++column, parent), item); + }, std::make_index_sequence<static_column_count>()); } } bool autoConnectPropertiesInRow(const row_type &row, int rowIndex, const QModelIndex &parent) const { if (!QRangeModelDetails::isValid(row)) - return false; + return true; // nothing to do return forEachColumn(row, rowIndex, parent, [this](const QModelIndex &index, QObject *item) { if constexpr (isMutable()) return Self::connectProperties(index, item, m_data.context, m_data.properties); @@ -2362,6 +2381,7 @@ protected: return that().childRangeImpl(index); } + template <typename, typename, typename> friend class QRangeModelAdapter; ModelData m_data; }; @@ -2395,6 +2415,26 @@ public: : Base(std::forward<Range>(model), std::forward<Protocol>(p), itemModel) {}; + void setParentRow(range_type &children, row_ptr parent) + { + for (auto &&child : children) + this->protocol().setParentRow(QRangeModelDetails::refTo(child), parent); + resetParentInChildren(&children); + } + + void deleteRemovedRows(range_type &range) + { + deleteRemovedRows(QRangeModelDetails::begin(range), QRangeModelDetails::end(range)); + } + + bool autoConnectProperties(const QModelIndex &parent) const + { + auto *children = this->childRange(parent); + if (!children) + return true; + return autoConnectPropertiesRange(QRangeModelDetails::refTo(children), parent); + } + protected: QModelIndex indexImpl(int row, int column, const QModelIndex &parent) const { @@ -2627,9 +2667,11 @@ protected: return false; Q_ASSERT(QRangeModelDetails::isValid(row)); const auto &children = this->protocol().childRows(QRangeModelDetails::refTo(row)); - if (!autoConnectPropertiesRange(children, - this->itemModel().index(rowIndex, 0, parent))) { - return false; + if (QRangeModelDetails::isValid(children)) { + if (!autoConnectPropertiesRange(QRangeModelDetails::refTo(children), + this->itemModel().index(rowIndex, 0, parent))) { + return false; + } } ++rowIndex; } diff --git a/src/corelib/itemmodels/qrangemodeladapter.h b/src/corelib/itemmodels/qrangemodeladapter.h new file mode 100644 index 00000000000..0234c402248 --- /dev/null +++ b/src/corelib/itemmodels/qrangemodeladapter.h @@ -0,0 +1,1751 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default + +#ifndef QRANGEMODELADAPTER_H +#define QRANGEMODELADAPTER_H + +#include <QtCore/qrangemodeladapter_impl.h> + +QT_BEGIN_NAMESPACE + +template <typename Range, typename Protocol = void, typename Model = QRangeModel> +class QT_TECH_PREVIEW_API QRangeModelAdapter +{ + using Impl = QRangeModelDetails::RangeImplementation<Range, Protocol>; + using Storage = QRangeModelDetails::AdapterStorage<Model, Impl>; + + Storage storage; + +#ifdef Q_QDOC + using range_type = Range; +#else + using range_type = QRangeModelDetails::wrapped_t<Range>; +#endif + using const_row_reference = typename Impl::const_row_reference; + using row_reference = typename Impl::row_reference; + using range_features = typename QRangeModelDetails::range_traits<range_type>; + using row_type = std::remove_reference_t<row_reference>; + using row_features = QRangeModelDetails::range_traits<typename Impl::wrapped_row_type>; + using row_ptr = typename Impl::wrapped_row_type *; + using row_traits = typename Impl::row_traits; + using item_type = std::remove_reference_t<typename row_traits::item_type>; + using data_type = typename QRangeModelDetails::data_type<item_type>::type; + using const_data_type = QRangeModelDetails::asConst_t<data_type>; + using protocol_traits = typename Impl::protocol_traits; + + template <typename I> static constexpr bool is_list = I::protocol_traits::is_list; + template <typename I> static constexpr bool is_table = I::protocol_traits::is_table; + template <typename I> static constexpr bool is_tree = I::protocol_traits::is_tree; + template <typename I> static constexpr bool canInsertColumns = I::dynamicColumns() + && I::isMutable() + && row_features::has_insert; + template <typename I> static constexpr bool canRemoveColumns = I::dynamicColumns() + && I::isMutable() + && row_features::has_erase; + + template <typename I> using if_writable = std::enable_if_t<I::isMutable(), bool>; + template <typename I> using if_list = std::enable_if_t<is_list<I>, bool>; + template <typename I> using unless_list = std::enable_if_t<!is_list<I>, bool>; + template <typename I> using if_table = std::enable_if_t<is_table<I>, bool>; + template <typename I> using if_tree = std::enable_if_t<is_tree<I>, bool>; + template <typename I> using unless_tree = std::enable_if_t<!is_tree<I>, bool>; + template <typename I> using if_flat = std::enable_if_t<is_list<I> || is_table<I>, bool>; + + template <typename I> + using if_canInsertRows = std::enable_if_t<I::canInsertRows(), bool>; + template <typename I> + using if_canRemoveRows = std::enable_if_t<I::canRemoveRows(), bool>; + template <typename F> + using if_canMoveItems = std::enable_if_t<F::has_rotate || F::has_splice, bool>; + + template <typename I> + using if_canInsertColumns = std::enable_if_t<canInsertColumns<I>, bool>; + template <typename I> + using if_canRemoveColumns = std::enable_if_t<canRemoveColumns<I>, bool>; + + template <typename Row> + static constexpr bool is_compatible_row = std::is_convertible_v<Row, const_row_reference>; + template <typename Row> + using if_compatible_row = std::enable_if_t<is_compatible_row<Row>, bool>; + + template <typename C> + static constexpr bool is_compatible_row_range = is_compatible_row< + decltype(*std::begin(std::declval<C&>())) + >; + template <typename C> + using if_compatible_row_range = std::enable_if_t<is_compatible_row_range<C>, bool>; + template <typename Data> + static constexpr bool is_compatible_data = std::is_convertible_v<Data, data_type>; + template <typename Data> + using if_compatible_data = std::enable_if_t<is_compatible_data<Data>, bool>; + template <typename C> + static constexpr bool is_compatible_data_range = is_compatible_data< + typename QRangeModelDetails::data_type< + typename QRangeModelDetails::row_traits< + decltype(*std::begin(std::declval<C&>())) + >::item_type + >::type + >; + template <typename C> + using if_compatible_column_data = std::enable_if_t<is_compatible_data<C> + || is_compatible_data_range<C>, bool>; + template <typename C> + using if_compatible_column_range = std::enable_if_t<is_compatible_data_range<C>, bool>; + + template <typename R> + using if_assignable_range = std::enable_if_t<std::is_assignable_v<range_type, R>, bool>; + + friend class QRangeModel; + template <typename T> + static constexpr bool is_adapter = QRangeModelDetails::is_any_of<q20::remove_cvref_t<T>, + QRangeModelAdapter>::value; + template <typename T> + using unless_adapter = std::enable_if_t<!is_adapter<T>, bool>; + +#if !defined(Q_OS_VXWORKS) && !defined(Q_OS_INTEGRITY) + // An adapter on a mutable range can make itself an adapter on a const + // version of that same range. To make the constructor for a sub-range + // accessible, befriend the mutable version. We can use more + // generic pattern matching here, as we only use as input what asConst + // might produce as output. + template <typename T> static constexpr T asMutable(const T &); + template <typename T> static constexpr T *asMutable(const T *); + template <template <typename, typename...> typename U, typename T, typename ...Args> + static constexpr U<T, Args...> asMutable(const U<const T, Args...> &); + + template <typename T> + using asMutable_t = decltype(asMutable(std::declval<T>())); + friend class QRangeModelAdapter<asMutable_t<Range>, Protocol, Model>; +#else + template <typename R, typename P, typename M> + friend class QRangeModelAdapter; +#endif + + explicit QRangeModelAdapter(const std::shared_ptr<QRangeModel> &model, const QModelIndex &root, + std::in_place_t) // disambiguate from range/protocol c'tor + : storage{model, root} + {} + + explicit QRangeModelAdapter(QRangeModel *model) + : storage(model) + {} + +public: + struct DataReference + { + using value_type = data_type; + using const_value_type = const_data_type; + using pointer = QRangeModelDetails::data_pointer_t<const_value_type>; + + explicit DataReference(const QModelIndex &index) noexcept + : m_index(index) + { + Q_ASSERT_X(m_index.isValid(), "QRangeModelAdapter::at", "Index at position is invalid"); + } + + DataReference(const DataReference &other) = default; + DataReference(DataReference &&other) = default; + + // reference (not std::reference_wrapper) semantics + DataReference &operator=(const DataReference &other) + { + *this = other.get(); + return *this; + } + + DataReference &operator=(DataReference &&other) + { + *this = other.get(); + return *this; + } + + ~DataReference() = default; + + DataReference &operator=(const value_type &value) + { + assign(value); + return *this; + } + + DataReference &operator=(value_type &&value) + { + assign(std::move(value)); + return *this; + } + + const_value_type get() const + { + Q_ASSERT_X(m_index.isValid(), "QRangeModelAdapter::at", "Index at position is invalid"); + return QRangeModelDetails::dataAtIndex<q20::remove_cvref_t<value_type>>(m_index); + } + + operator const_value_type() const + { + return get(); + } + + pointer operator->() const + { + return {get()}; + } + + bool isValid() const { return m_index.isValid(); } + + private: + QModelIndex m_index; + + template <typename Value> + void assign(Value &&value) + { + constexpr Qt::ItemDataRole dataRole = Qt::RangeModelAdapterRole; + + if (m_index.isValid()) { + auto model = const_cast<QAbstractItemModel *>(m_index.model()); + [[maybe_unused]] bool couldWrite = false; + if constexpr (std::is_same_v<q20::remove_cvref_t<Value>, QVariant>) { + couldWrite = model->setData(m_index, value, dataRole); + } else { + couldWrite = model->setData(m_index, + QVariant::fromValue(std::forward<Value>(value)), + dataRole); + } +#ifndef QT_NO_DEBUG + if (!couldWrite) { + qWarning() << "Writing value of type" + << QMetaType::fromType<q20::remove_cvref_t<Value>>().name() + << "to role" << dataRole << "at index" << m_index << "failed"; + } + } else { + qCritical("Data reference for invalid index, can't write to model"); +#endif + } + } + + friend inline bool comparesEqual(const DataReference &lhs, const DataReference &rhs) + { + return lhs.m_index == rhs.m_index + || lhs.get() == rhs.get(); + } + Q_DECLARE_EQUALITY_COMPARABLE_NON_NOEXCEPT(DataReference); + + friend inline bool comparesEqual(const DataReference &lhs, const value_type &rhs) + { + return lhs.get() == rhs; + } + Q_DECLARE_EQUALITY_COMPARABLE_NON_NOEXCEPT(DataReference, value_type); + + friend inline void swap(DataReference lhs, DataReference rhs) + { + const value_type lhsValue = lhs.get(); + lhs = rhs; + rhs = lhsValue; // no point in moving, we have to go through QVariant anyway + } + +#ifndef QT_NO_DEBUG_STREAM + friend inline QDebug operator<<(QDebug dbg, const DataReference &ref) + { + return dbg << ref.get(); + } +#endif +#ifndef QT_NO_DATASTREAM + friend inline QDataStream &operator<<(QDataStream &ds, const DataReference &ref) + { + return ds << ref.get(); + } + friend inline QDataStream &operator>>(QDataStream &ds, DataReference &ref) + { + value_type value; + ds >> value; + ref = value; + return ds; + } +#endif + }; + template <typename Iterator, typename Adapter> + struct ColumnIteratorBase + { + using iterator_category = std::random_access_iterator_tag; + using difference_type = int; + + ColumnIteratorBase() = default; + ColumnIteratorBase(const ColumnIteratorBase &other) = default; + ColumnIteratorBase(ColumnIteratorBase &&other) = default; + ColumnIteratorBase &operator=(const ColumnIteratorBase &other) = default; + ColumnIteratorBase &operator=(ColumnIteratorBase &&other) = default; + + ColumnIteratorBase(const QModelIndex &rowIndex, int column, Adapter *adapter) noexcept + : m_rowIndex(rowIndex), m_column(column), m_adapter(adapter) + { + } + + void swap(ColumnIteratorBase &other) noexcept + { + using std::swap; + swap(m_rowIndex, other.m_rowIndex); + swap(m_column, other.m_column); + q_ptr_swap(m_adapter, other.m_adapter); + } + + friend Iterator &operator++(Iterator &that) noexcept + { + ++that.m_column; + return that; + } + friend Iterator operator++(Iterator &that, int) noexcept + { + auto copy = that; + ++that; + return copy; + } + friend Iterator operator+(const Iterator &that, difference_type n) noexcept + { + return {that.m_rowIndex, that.m_column + n, that.m_adapter}; + } + friend Iterator operator+(difference_type n, const Iterator &that) noexcept + { + return that + n; + } + friend Iterator &operator+=(Iterator &that, difference_type n) noexcept + { + that.m_column += n; + return that; + } + + friend Iterator &operator--(Iterator &that) noexcept + { + --that.m_column; + return that; + } + friend Iterator operator--(Iterator &that, int) noexcept + { + auto copy = that; + --that; + return copy; + } + friend Iterator operator-(const Iterator &that, difference_type n) noexcept + { + return {that.m_rowIndex, that.m_column - n, that.m_adapter}; + } + friend Iterator operator-(difference_type n, const Iterator &that) noexcept + { + return that - n; + } + friend Iterator &operator-=(Iterator &that, difference_type n) noexcept + { + that.m_column -= n; + return that; + } + + friend difference_type operator-(const Iterator &lhs, const Iterator &rhs) noexcept + { + Q_PRE(lhs.m_rowIndex == rhs.m_rowIndex); + Q_PRE(lhs.m_adapter == rhs.m_adapter); + return lhs.m_column - rhs.m_column; + } + + protected: + ~ColumnIteratorBase() = default; + QModelIndex m_rowIndex; + int m_column = -1; + Adapter *m_adapter = nullptr; + + private: + friend bool comparesEqual(const Iterator &lhs, const Iterator &rhs) + { + Q_ASSERT(lhs.m_rowIndex == rhs.m_rowIndex); + return lhs.m_column == rhs.m_column; + } + friend Qt::strong_ordering compareThreeWay(const Iterator &lhs, const Iterator &rhs) + { + Q_ASSERT(lhs.m_rowIndex == rhs.m_rowIndex); + return qCompareThreeWay(lhs.m_column, rhs.m_column); + } + + Q_DECLARE_STRONGLY_ORDERED_NON_NOEXCEPT(Iterator) + +#ifndef QT_NO_DEBUG_STREAM + friend inline QDebug operator<<(QDebug dbg, const Iterator &it) + { + QDebugStateSaver saver(dbg); + dbg.nospace(); + return dbg << "ColumnIterator(" << it.m_rowIndex.siblingAtColumn(it.m_column) << ")"; + } +#endif + }; + + struct ConstColumnIterator : ColumnIteratorBase<ConstColumnIterator, const QRangeModelAdapter> + { + using Base = ColumnIteratorBase<ConstColumnIterator, const QRangeModelAdapter>; + using difference_type = typename Base::difference_type; + using value_type = data_type; + using reference = const_data_type; + using pointer = QRangeModelDetails::data_pointer_t<value_type>; + + using Base::Base; + using Base::operator=; + ~ConstColumnIterator() = default; + + pointer operator->() const + { + return pointer{operator*()}; + } + + reference operator*() const + { + return std::as_const(this->m_adapter)->at(this->m_rowIndex.row(), this->m_column); + } + + reference operator[](difference_type n) const + { + return *(*this + n); + } + }; + + struct ColumnIterator : ColumnIteratorBase<ColumnIterator, QRangeModelAdapter> + { + using Base = ColumnIteratorBase<ColumnIterator, QRangeModelAdapter>; + using difference_type = typename Base::difference_type; + using value_type = DataReference; + using reference = DataReference; + using pointer = reference; + + using Base::Base; + using Base::operator=; + ~ColumnIterator() = default; + + operator ConstColumnIterator() const + { + return ConstColumnIterator{this->m_rowIndex, this->m_column, this->m_adapter}; + } + + pointer operator->() const + { + return operator*(); + } + + reference operator*() const + { + return reference{this->m_rowIndex.siblingAtColumn(this->m_column)}; + } + + reference operator[](difference_type n) const + { + return *(*this + n); + } + }; + + template <typename Reference, typename const_row_type, typename = void> + struct RowGetter + { + const_row_type get() const + { + using namespace QRangeModelDetails; + const Reference *that = static_cast<const Reference *>(this); + const auto *impl = that->m_adapter->storage.implementation(); + auto *childRange = impl->childRange(that->m_index.parent()); + if constexpr (std::is_convertible_v<const row_type &, const_row_type>) { + return *std::next(QRangeModelDetails::begin(childRange), that->m_index.row()); + } else { + const auto &row = *std::next(QRangeModelDetails::begin(childRange), + that->m_index.row()); + return const_row_type{QRangeModelDetails::begin(row), QRangeModelDetails::end(row)}; + } + } + + const_row_type operator->() const + { + return {get()}; + } + + operator const_row_type() const + { + return get(); + } + }; + + template <typename Reference, typename const_row_type> + struct RowGetter<Reference, const_row_type, + std::enable_if_t<std::is_reference_v<const_row_type>>> + { + const_row_type get() const + { + using namespace QRangeModelDetails; + using QRangeModelDetails::begin; + + const Reference *that = static_cast<const Reference *>(this); + const auto *impl = that->m_adapter->storage.implementation(); + return *std::next(begin(refTo(impl->childRange(that->m_index.parent()))), + that->m_index.row()); + } + + auto operator->() const + { + return std::addressof(get()); + } + + operator const_row_type() const + { + return get(); + } + }; + + template <typename Reference, typename const_row_type> + struct RowGetter<Reference, const_row_type, + std::enable_if_t<std::is_pointer_v<const_row_type>>> + { + const_row_type get() const + { + using namespace QRangeModelDetails; + using QRangeModelDetails::begin; + + const Reference *that = static_cast<const Reference *>(this); + const auto *impl = that->m_adapter->storage.implementation(); + return *std::next(begin(refTo(impl->childRange(that->m_index.parent()))), + that->m_index.row()); + } + + const_row_type operator->() const + { + return get(); + } + + operator const_row_type() const + { + return get(); + } + }; + + template <typename Reference, typename Adapter> + struct RowReferenceBase : RowGetter<Reference, QRangeModelDetails::asConstRow_t<row_type>> + { + using const_iterator = ConstColumnIterator; + using size_type = int; + using difference_type = int; + using const_row_type = QRangeModelDetails::asConstRow_t<row_type>; + + RowReferenceBase(const QModelIndex &index, Adapter *adapter) noexcept + : m_index(index), m_adapter(adapter) + {} + + template <typename I = Impl, if_tree<I> = true> + bool hasChildren() const + { + return m_adapter->model()->hasChildren(m_index); + } + + template <typename I = Impl, if_tree<I> = true> + auto children() const + { + using ConstRange = QRangeModelDetails::asConst_t<Range>; + return QRangeModelAdapter<ConstRange, Protocol, Model>(m_adapter->storage.m_model, + m_index, std::in_place); + } + + ConstColumnIterator cbegin() const + { + return ConstColumnIterator{m_index, 0, m_adapter}; + } + ConstColumnIterator cend() const + { + return ConstColumnIterator{m_index, m_adapter->columnCount(), m_adapter}; + } + + ConstColumnIterator begin() const { return cbegin(); } + ConstColumnIterator end() const { return cend(); } + + size_type size() const + { + return m_adapter->columnCount(); + } + + auto at(int column) const + { + Q_ASSERT(column >= 0 && column < m_adapter->columnCount()); + return *ConstColumnIterator{m_index, column, m_adapter}; + } + + auto operator[](int column) const + { + return at(column); + } + + protected: + friend struct RowGetter<Reference, const_row_type>; + ~RowReferenceBase() = default; + QModelIndex m_index; + Adapter *m_adapter; + + private: + friend bool comparesEqual(const Reference &lhs, const Reference &rhs) + { + Q_ASSERT(lhs.m_adapter == rhs.m_adapter); + return lhs.m_index == rhs.m_index; + } + friend Qt::strong_ordering compareThreeWay(const Reference &lhs, const Reference &rhs) + { + Q_ASSERT(lhs.m_adapter == rhs.m_adapter); + return qCompareThreeWay(lhs.m_index, rhs.m_index); + } + + Q_DECLARE_STRONGLY_ORDERED_NON_NOEXCEPT(Reference) + + friend bool comparesEqual(const Reference &lhs, const row_type &rhs) + { + return lhs.get() == rhs; + } + Q_DECLARE_EQUALITY_COMPARABLE_NON_NOEXCEPT(Reference, row_type) + +#ifndef QT_NO_DEBUG_STREAM + friend inline QDebug operator<<(QDebug dbg, const Reference &ref) + { + QDebugStateSaver saver(dbg); + dbg.nospace(); + return dbg << "RowReference(" << ref.m_index << ")"; + } +#endif +#ifndef QT_NO_DATASTREAM + friend inline QDataStream &operator<<(QDataStream &ds, const Reference &ref) + { + return ds << ref.get(); + } +#endif + }; + + struct ConstRowReference : RowReferenceBase<ConstRowReference, const QRangeModelAdapter> + { + using Base = RowReferenceBase<ConstRowReference, const QRangeModelAdapter>; + using Base::Base; + + ConstRowReference() = default; + ConstRowReference(const ConstRowReference &) = default; + ConstRowReference(ConstRowReference &&) = default; + ConstRowReference &operator=(const ConstRowReference &) = default; + ConstRowReference &operator=(ConstRowReference &&) = default; + ~ConstRowReference() = default; + }; + + struct RowReference : RowReferenceBase<RowReference, QRangeModelAdapter> + { + using Base = RowReferenceBase<RowReference, QRangeModelAdapter>; + using iterator = ColumnIterator; + using const_iterator = typename Base::const_iterator; + using size_type = typename Base::size_type; + using difference_type = typename Base::difference_type; + using const_row_type = typename Base::const_row_type; + + using Base::Base; + RowReference() = delete; + ~RowReference() = default; + RowReference(const RowReference &other) = default; + RowReference(RowReference &&other) = default; + + // assignment has reference (std::reference_wrapper) semantics + RowReference &operator=(const ConstRowReference &other) + { + *this = other.get(); + return *this; + } + + RowReference &operator=(const RowReference &other) + { + *this = other.get(); + return *this; + } + + RowReference &operator=(const row_type &other) + { + assign(other); + return *this; + } + + RowReference &operator=(row_type &&other) + { + assign(std::move(other)); + return *this; + } + + operator ConstRowReference() const + { + return ConstRowReference{this->m_index, this->m_adapter}; + } + + template <typename ConstRowType = const_row_type, + std::enable_if_t<!std::is_same_v<ConstRowType, const row_type &>, bool> = true> + RowReference &operator=(const ConstRowType &other) + { + assign(other); + return *this; + } + + template <typename T, typename It, typename Sentinel> + RowReference &operator=(const QRangeModelDetails::RowView<T, It, Sentinel> &other) + { + *this = row_type{other.begin(), other.end()}; + return *this; + } + + friend inline void swap(RowReference lhs, RowReference rhs) + { + auto lhsRow = lhs.get(); + lhs = rhs.get(); + rhs = std::move(lhsRow); + } + + template <typename I = Impl, if_tree<I> = true> + auto children() + { + return QRangeModelAdapter(this->m_adapter->storage.m_model, this->m_index, + std::in_place); + } + + using Base::begin; + ColumnIterator begin() + { + return ColumnIterator{this->m_index, 0, this->m_adapter}; + } + + using Base::end; + ColumnIterator end() + { + return ColumnIterator{this->m_index, this->m_adapter->columnCount(), this->m_adapter}; + } + + using Base::at; + auto at(int column) + { + Q_ASSERT(column >= 0 && column < this->m_adapter->columnCount()); + return *ColumnIterator{this->m_index, column, this->m_adapter}; + } + + using Base::operator[]; + auto operator[](int column) + { + return at(column); + } + + private: + template <typename RHS> + void verifyRows(const row_type &oldRow, const RHS &newRow) + { + using namespace QRangeModelDetails; + if constexpr (test_size<row_type>::value) { + // prevent that tables get populated with wrongly sized rows + Q_ASSERT_X(Impl::size(newRow) == Impl::size(oldRow), + "RowReference::operator=()", + "The new row has the wrong size!"); + } + + if constexpr (is_tree<Impl>) { + // we cannot hook invalid rows up to the tree hierarchy + Q_ASSERT_X(isValid(newRow), + "RowReference::operator=()", + "An invalid row can not inserted into a tree!"); + } + } + + template <typename R> + void assign(R &&other) + { + auto *impl = this->m_adapter->storage.implementation(); + decltype(auto) oldRow = impl->rowData(this->m_index); + + verifyRows(oldRow, other); + + if constexpr (is_tree<Impl>) { + using namespace QRangeModelDetails; + auto &protocol = impl->protocol(); + auto *oldParent = protocol.parentRow(refTo(oldRow)); + + // the old children will be removed; we don't try to overwrite + // them with the new children, we replace them completely + if (decltype(auto) oldChildren = protocol.childRows(refTo(oldRow)); + isValid(oldChildren)) { + if (int oldChildCount = this->m_adapter->model()->rowCount(this->m_index)) { + impl->beginRemoveRows(this->m_index, 0, oldChildCount - 1); + impl->deleteRemovedRows(refTo(oldChildren)); + // make sure the list is empty before we emit rowsRemoved + refTo(oldChildren) = range_type{}; + impl->endRemoveRows(); + } + } + + if constexpr (protocol_traits::has_deleteRow) + protocol.deleteRow(oldRow); + oldRow = std::forward<R>(other); + if constexpr (protocol_traits::has_setParentRow) { + protocol.setParentRow(refTo(oldRow), oldParent); + if (decltype(auto) newChildren = protocol.childRows(refTo(oldRow)); + isValid(newChildren)) { + impl->beginInsertRows(this->m_index, 0, Impl::size(refTo(newChildren)) - 1); + impl->setParentRow(refTo(newChildren), pointerTo(oldRow)); + impl->endInsertRows(); + } + } + } else { + oldRow = std::forward<R>(other); + } + this->m_adapter->emitDataChanged(this->m_index, + this->m_index.siblingAtColumn(this->m_adapter->columnCount() - 1)); + if constexpr (Impl::itemsAreQObjects) { + if (this->m_adapter->model()->autoConnectPolicy() == QRangeModel::AutoConnectPolicy::Full) { + impl->autoConnectPropertiesInRow(oldRow, this->m_index.row(), this->m_index.parent()); + if constexpr (is_tree<Impl>) + impl->autoConnectProperties(this->m_index); + } + + } + } + +#ifndef QT_NO_DATASTREAM + friend inline QDataStream &operator>>(QDataStream &ds, RowReference &ref) + { + row_type value; + ds >> value; + ref = value; + return ds; + } +#endif + }; + + template <typename Iterator, typename Adapter> + struct RowIteratorBase : QRangeModelDetails::ParentIndex<is_tree<Impl>> + { + using iterator_category = std::random_access_iterator_tag; + using difference_type = int; + + RowIteratorBase() = default; + RowIteratorBase(const RowIteratorBase &) = default; + RowIteratorBase(RowIteratorBase &&) = default; + RowIteratorBase &operator=(const RowIteratorBase &) = default; + RowIteratorBase &operator=(RowIteratorBase &&) = default; + + RowIteratorBase(int row, const QModelIndex &parent, Adapter *adapter) + : QRangeModelDetails::ParentIndex<is_tree<Impl>>{parent} + , m_row(row), m_adapter(adapter) + {} + + void swap(RowIteratorBase &other) noexcept + { + using std::swap; + swap(m_row, other.m_row); + swap(this->m_rootIndex, other.m_rootIndex); + q_ptr_swap(m_adapter, other.m_adapter); + } + + friend Iterator &operator++(Iterator &that) noexcept + { + ++that.m_row; + return that; + } + friend Iterator operator++(Iterator &that, int) noexcept + { + auto copy = that; + ++that; + return copy; + } + friend Iterator operator+(const Iterator &that, difference_type n) noexcept + { + return {that.m_row + n, that.root(), that.m_adapter}; + } + friend Iterator operator+(difference_type n, const Iterator &that) noexcept + { + return that + n; + } + friend Iterator &operator+=(Iterator &that, difference_type n) noexcept + { + that.m_row += n; + return that; + } + + friend Iterator &operator--(Iterator &that) noexcept + { + --that.m_row; + return that; + } + friend Iterator operator--(Iterator &that, int) noexcept + { + auto copy = that; + --that; + return copy; + } + friend Iterator operator-(const Iterator &that, difference_type n) noexcept + { + return {that.m_row - n, that.root(), that.m_adapter}; + } + friend Iterator operator-(difference_type n, const Iterator &that) noexcept + { + return that - n; + } + friend Iterator &operator-=(Iterator &that, difference_type n) noexcept + { + that.m_row -= n; + return that; + } + + friend difference_type operator-(const Iterator &lhs, const Iterator &rhs) noexcept + { + return lhs.m_row - rhs.m_row; + } + + protected: + ~RowIteratorBase() = default; + int m_row = -1; + Adapter *m_adapter = nullptr; + + private: + friend bool comparesEqual(const Iterator &lhs, const Iterator &rhs) noexcept + { + return lhs.m_row == rhs.m_row && lhs.root() == rhs.root(); + } + friend Qt::strong_ordering compareThreeWay(const Iterator &lhs, const Iterator &rhs) noexcept + { + if (lhs.root() == rhs.root()) + return qCompareThreeWay(lhs.m_row, rhs.m_row); + return qCompareThreeWay(lhs.root(), rhs.root()); + } + + Q_DECLARE_STRONGLY_ORDERED(Iterator) + +#ifndef QT_NO_DEBUG_STREAM + friend inline QDebug operator<<(QDebug dbg, const Iterator &it) + { + QDebugStateSaver saver(dbg); + dbg.nospace(); + return dbg << "RowIterator(" << it.m_row << it.root() << ")"; + } +#endif + }; + +public: + struct ConstRowIterator : public RowIteratorBase<ConstRowIterator, const QRangeModelAdapter> + { + using Base = RowIteratorBase<ConstRowIterator, const QRangeModelAdapter>; + using Base::Base; + + using difference_type = typename Base::difference_type; + using value_type = std::conditional_t<is_list<Impl>, + const_data_type, + ConstRowReference>; + using reference = std::conditional_t<is_list<Impl>, + const_data_type, + ConstRowReference>; + using pointer = std::conditional_t<is_list<Impl>, + QRangeModelDetails::data_pointer_t<const_data_type>, + ConstRowReference>; + + ConstRowIterator(const ConstRowIterator &other) = default; + ConstRowIterator(ConstRowIterator &&other) = default; + ConstRowIterator &operator=(const ConstRowIterator &other) = default; + ConstRowIterator &operator=(ConstRowIterator &&other) = default; + ~ConstRowIterator() = default; + + pointer operator->() const + { + return pointer{operator*()}; + } + + reference operator*() const + { + if constexpr (is_list<Impl>) { + return this->m_adapter->at(this->m_row); + } else { + const QModelIndex index = this->m_adapter->model()->index(this->m_row, 0, + this->root()); + return ConstRowReference{index, this->m_adapter}; + } + } + + reference operator[](difference_type n) const + { + return *(*this + n); + } + }; + + struct RowIterator : public RowIteratorBase<RowIterator, QRangeModelAdapter> + { + using Base = RowIteratorBase<RowIterator, QRangeModelAdapter>; + using Base::Base; + + using difference_type = typename Base::difference_type; + using value_type = std::conditional_t<is_list<Impl>, + DataReference, + RowReference>; + using reference = std::conditional_t<is_list<Impl>, + DataReference, + RowReference>; + using pointer = std::conditional_t<is_list<Impl>, + DataReference, + RowReference>; + + RowIterator(const RowIterator &other) = default; + RowIterator(RowIterator &&other) = default; + RowIterator &operator=(const RowIterator &other) = default; + RowIterator &operator=(RowIterator &&other) = default; + ~RowIterator() = default; + + operator ConstRowIterator() const + { + return ConstRowIterator{this->m_row, this->root(), this->m_adapter}; + } + + pointer operator->() const + { + return pointer{operator*()}; + } + + reference operator*() const + { + const QModelIndex index = this->m_adapter->model()->index(this->m_row, 0, this->root()); + if constexpr (is_list<Impl>) { + return reference{index}; + } else { + return reference{index, this->m_adapter}; + } + } + + reference operator[](difference_type n) const + { + return *(*this + n); + } + }; + + using const_iterator = ConstRowIterator; + using iterator = RowIterator; + + template <typename R, typename P, + std::enable_if_t<!std::is_void_v<P>, bool> = true> + explicit QRangeModelAdapter(R &&range, P &&protocol) + : QRangeModelAdapter(new Model(std::forward<R>(range), std::forward<P>(protocol))) + {} + + template <typename R, typename P = void, + unless_adapter<R> = true, + std::enable_if_t<std::is_void_v<P>, bool> = true> + explicit QRangeModelAdapter(R &&range) + : QRangeModelAdapter(new Model(std::forward<R>(range))) + {} + + // compiler-generated copy/move SMF are fine! + + Model *model() const + { + return storage.m_model.get(); + } + + const range_type &range() const + { + return QRangeModelDetails::refTo(storage.implementation()->childRange(storage.root())); + } + + Q_IMPLICIT operator const range_type &() const + { + return range(); + } + + template <typename NewRange = range_type, if_assignable_range<NewRange> = true> + void setRange(NewRange &&newRange) + { + setRangeImpl(qsizetype(Impl::size(QRangeModelDetails::refTo(newRange))) - 1, + [&newRange](auto &oldRange) { + oldRange = std::forward<NewRange>(newRange); + }); + } + + template <typename NewRange = range_type, if_assignable_range<NewRange> = true, + unless_adapter<NewRange> = true> + QRangeModelAdapter &operator=(NewRange &&newRange) + { + setRange(std::forward<NewRange>(newRange)); + return *this; + } + + template <typename Row, if_assignable_range<std::initializer_list<Row>> = true> + void setRange(std::initializer_list<Row> newRange) + { + setRangeImpl(qsizetype(newRange.size() - 1), [&newRange](auto &oldRange) { + oldRange = newRange; + }); + } + + template <typename Row, if_assignable_range<std::initializer_list<Row>> = true> + QRangeModelAdapter &operator=(std::initializer_list<Row> newRange) + { + setRange(newRange); + return *this; + } + + template <typename Row, if_assignable_range<std::initializer_list<Row>> = true> + void assign(std::initializer_list<Row> newRange) + { + setRange(newRange); + } + + template <typename InputIterator, typename Sentinel, typename I = Impl, if_writable<I> = true> + void setRange(InputIterator first, Sentinel last) + { + setRangeImpl(qsizetype(std::distance(first, last) - 1), [first, last](auto &oldRange) { + oldRange.assign(first, last); + }); + } + + template <typename InputIterator, typename Sentinel, typename I = Impl, if_writable<I> = true> + void assign(InputIterator first, Sentinel last) + { + setRange(first, last); + } + + // iterator API + ConstRowIterator cbegin() const + { + return ConstRowIterator{ 0, storage.root(), this }; + } + ConstRowIterator begin() const { return cbegin(); } + + ConstRowIterator cend() const + { + return ConstRowIterator{ rowCount(), storage.root(), this }; + } + ConstRowIterator end() const { return cend(); } + + template <typename I = Impl, if_writable<I> = true> + RowIterator begin() + { + return RowIterator{ 0, storage.root(), this }; + } + + template <typename I = Impl, if_writable<I> = true> + RowIterator end() + { + return RowIterator{ rowCount(), storage.root(), this }; + } + + int size() const + { + return rowCount(); + } + + template <typename I = Impl, if_list<I> = true> + QModelIndex index(int row) const + { + return storage->index(row, 0, storage.root()); + } + + template <typename I = Impl, unless_list<I> = true> + QModelIndex index(int row, int column) const + { + return storage->index(row, column, storage.root()); + } + + template <typename I = Impl, if_tree<I> = true> + QModelIndex index(QSpan<const int> path, int col) const + { + Q_PRE(path.size()); + QModelIndex result = storage.root(); + auto count = path.size(); + for (const int r : path) { + if (--count) + result = storage->index(r, 0, result); + else + result = storage->index(r, col, result); + } + return result; + } + + int columnCount() const + { + // all rows and tree branches have the same column count + return storage->columnCount({}); + } + + int rowCount() const + { + return storage->rowCount(storage.root()); + } + + template <typename I = Impl, if_tree<I> = true> + int rowCount(int row) const + { + return storage->rowCount(index(row, 0)); + } + + template <typename I = Impl, if_tree<I> = true> + int rowCount(QSpan<const int> path) const + { + return storage->rowCount(index(path, 0)); + } + + template <typename I = Impl, if_tree<I> = true> + constexpr bool hasChildren(int row) const + { + return storage.m_model->hasChildren(index(row, 0)); + } + + template <typename I = Impl, if_tree<I> = true> + constexpr bool hasChildren(QSpan<const int> path) const + { + return storage.m_model->hasChildren(index(path, 0)); + } + + template <typename I = Impl, if_list<I> = true> + QVariant data(int row) const + { + return QRangeModelDetails::dataAtIndex<QVariant>(index(row)); + } + + template <typename I = Impl, if_list<I> = true> + QVariant data(int row, int role) const + { + return QRangeModelDetails::dataAtIndex<QVariant>(index(row), role); + } + + template <typename I = Impl, if_list<I> = true, if_writable<I> = true> + bool setData(int row, const QVariant &value, int role = Qt::EditRole) + { + return storage->setData(index(row), value, role); + } + + template <typename I = Impl, unless_list<I> = true> + QVariant data(int row, int column) const + { + return QRangeModelDetails::dataAtIndex<QVariant>(index(row, column)); + } + + template <typename I = Impl, unless_list<I> = true> + QVariant data(int row, int column, int role) const + { + return QRangeModelDetails::dataAtIndex<QVariant>(index(row, column), role); + } + + template <typename I = Impl, unless_list<I> = true, if_writable<I> = true> + bool setData(int row, int column, const QVariant &value, int role = Qt::EditRole) + { + return storage->setData(index(row, column), value, role); + } + + template <typename I = Impl, if_tree<I> = true> + QVariant data(QSpan<const int> path, int column) const + { + return QRangeModelDetails::dataAtIndex<QVariant>(index(path, column)); + } + + template <typename I = Impl, if_tree<I> = true> + QVariant data(QSpan<const int> path, int column, int role) const + { + return QRangeModelDetails::dataAtIndex<QVariant>(index(path, column), role); + } + + template <typename I = Impl, if_tree<I> = true, if_writable<I> = true> + bool setData(QSpan<const int> path, int column, const QVariant &value, int role = Qt::EditRole) + { + return storage->setData(index(path, column), value, role); + } + + // at/operator[int] for list: returns value at row + // if multi-role value: return the entire object + template <typename I= Impl, if_list<I> = true> + const_data_type at(int row) const + { + return QRangeModelDetails::dataAtIndex<data_type>(index(row)); + } + template <typename I = Impl, if_list<I> = true> + const_data_type operator[](int row) const { return at(row); } + + template <typename I= Impl, if_list<I> = true, if_writable<I> = true> + auto at(int row) { return DataReference{this->index(row)}; } + template <typename I = Impl, if_list<I> = true, if_writable<I> = true> + auto operator[](int row) { return DataReference{this->index(row)}; } + + // at/operator[int] for table or tree: a reference or view of the row + template <typename I = Impl, unless_list<I> = true> + decltype(auto) at(int row) const + { + return ConstRowReference{index(row, 0), this}.get(); + } + template <typename I = Impl, unless_list<I> = true> + decltype(auto) operator[](int row) const { return at(row); } + + template <typename I = Impl, if_table<I> = true, if_writable<I> = true> + auto at(int row) + { + return RowReference{index(row, 0), this}; + } + template <typename I = Impl, if_table<I> = true, if_writable<I> = true> + auto operator[](int row) { return at(row); } + + // at/operator[int, int] for table: returns value at row/column + template <typename I = Impl, unless_list<I> = true> + const_data_type at(int row, int column) const + { + return QRangeModelDetails::dataAtIndex<data_type>(index(row, column)); + } + +#ifdef __cpp_multidimensional_subscript + template <typename I = Impl, unless_list<I> = true> + const_data_type operator[](int row, int column) const { return at(row, column); } +#endif + + template <typename I = Impl, unless_list<I> = true, if_writable<I> = true> + auto at(int row, int column) + { + return DataReference{this->index(row, column)}; + } +#ifdef __cpp_multidimensional_subscript + template <typename I = Impl, unless_list<I> = true, if_writable<I> = true> + auto operator[](int row, int column) { return at(row, column); } +#endif + + // at/operator[int] for tree: return a wrapper that maintains reference to + // parent. + template <typename I = Impl, if_tree<I> = true, if_writable<I> = true> + auto at(int row) + { + return RowReference{index(row, 0), this}; + } + template <typename I = Impl, if_tree<I> = true, if_writable<I> = true> + auto operator[](int row) { return at(row); } + + // at/operator[path] for tree: a reference or view of the row + template <typename I = Impl, if_tree<I> = true> + decltype(auto) at(QSpan<const int> path) const + { + return ConstRowReference{index(path, 0), this}.get(); + } + template <typename I = Impl, if_tree<I> = true> + decltype(auto) operator[](QSpan<const int> path) const { return at(path); } + + template <typename I = Impl, if_tree<I> = true, if_writable<I> = true> + auto at(QSpan<const int> path) + { + return RowReference{index(path, 0), this}; + } + template <typename I = Impl, if_tree<I> = true, if_writable<I> = true> + auto operator[](QSpan<const int> path) { return at(path); } + + // at/operator[path, column] for tree: return value + template <typename I = Impl, if_tree<I> = true> + const_data_type at(QSpan<const int> path, int column) const + { + Q_PRE(path.size()); + return QRangeModelDetails::dataAtIndex<data_type>(index(path, column)); + } + +#ifdef __cpp_multidimensional_subscript + template <typename I = Impl, if_tree<I> = true> + const_data_type operator[](QSpan<const int> path, int column) const { return at(path, column); } +#endif + + template <typename I = Impl, if_tree<I> = true, if_writable<I> = true> + auto at(QSpan<const int> path, int column) + { + Q_PRE(path.size()); + return DataReference{this->index(path, column)}; + } +#ifdef __cpp_multidimensional_subscript + template <typename I = Impl, if_tree<I> = true, if_writable<I> = true> + auto operator[](QSpan<const int> path, int column) { return at(path, column); } +#endif + + template <typename I = Impl, if_canInsertRows<I> = true> + bool insertRow(int before) + { + return storage.m_model->insertRow(before); + } + + template <typename I = Impl, if_canInsertRows<I> = true, if_tree<I> = true> + bool insertRow(QSpan<const int> before) + { + Q_PRE(before.size()); + return storage.m_model->insertRow(before.back(), this->index(before.first(before.size() - 1), 0)); + } + + template <typename D = row_type, typename I = Impl, + if_canInsertRows<I> = true, if_compatible_row<D> = true> + bool insertRow(int before, D &&data) + { + return insertRowImpl(before, storage.root(), std::forward<D>(data)); + } + + template <typename D = row_type, typename I = Impl, + if_canInsertRows<I> = true, if_compatible_row<D> = true, if_tree<I> = true> + bool insertRow(QSpan<const int> before, D &&data) + { + return insertRowImpl(before, storage.root(), std::forward<D>(data)); + } + + template <typename C, typename I = Impl, + if_canInsertRows<I> = true, if_compatible_row_range<C> = true> + bool insertRows(int before, C &&data) + { + return insertRowsImpl(before, storage.root(), std::forward<C>(data)); + } + + template <typename C, typename I = Impl, + if_canInsertRows<I> = true, if_compatible_row_range<C> = true, if_tree<I> = true> + bool insertRows(QSpan<const int> before, C &&data) + { + return insertRowsImpl(before.back(), this->index(before.first(before.size() - 1), 0), + std::forward<C>(data)); + } + + template <typename I = Impl, if_canRemoveRows<I> = true> + bool removeRow(int row) + { + return removeRows(row, 1); + } + + template <typename I = Impl, if_canRemoveRows<I> = true, if_tree<I> = true> + bool removeRow(QSpan<const int> path) + { + return removeRows(path, 1); + } + + template <typename I = Impl, if_canRemoveRows<I> = true> + bool removeRows(int row, int count) + { + return storage->removeRows(row, count, storage.root()); + } + + template <typename I = Impl, if_canRemoveRows<I> = true, if_tree<I> = true> + bool removeRows(QSpan<const int> path, int count) + { + return storage->removeRows(path.back(), count, + this->index(path.first(path.size() - 1), 0)); + } + + template <typename F = range_features, if_canMoveItems<F> = true> + bool moveRow(int source, int destination) + { + return moveRows(source, 1, destination); + } + + template <typename F = range_features, if_canMoveItems<F> = true> + bool moveRows(int source, int count, int destination) + { + return storage->moveRows(storage.root(), source, count, storage.root(), destination); + } + + template <typename I = Impl, typename F = range_features, + if_canMoveItems<F> = true, if_tree<I> = true> + bool moveRow(QSpan<const int> source, QSpan<const int> destination) + { + return moveRows(source, 1, destination); + } + + template <typename I = Impl, typename F = range_features, + if_canMoveItems<F> = true, if_tree<I> = true> + bool moveRows(QSpan<const int> source, int count, QSpan<const int> destination) + { + return storage->moveRows(this->index(source.first(source.size() - 1), 0), + source.back(), + count, + this->index(destination.first(destination.size() - 1), 0), + destination.back()); + } + + template <typename I = Impl, if_canInsertColumns<I> = true> + bool insertColumn(int before) + { + return storage.m_model->insertColumn(before); + } + + template <typename D, typename I = Impl, + if_canInsertColumns<I> = true, if_compatible_column_data<D> = true> + bool insertColumn(int before, D &&data) + { + return insertColumnImpl(before, storage.root(), std::forward<D>(data)); + } + + template <typename C, typename I = Impl, + if_canInsertColumns<I> = true, if_compatible_column_range<C> = true> + bool insertColumns(int before, C &&data) + { + return insertColumnsImpl(before, storage.root(), std::forward<C>(data)); + } + + template <typename I = Impl, if_canRemoveColumns<I> = true> + bool removeColumn(int column) + { + return storage.m_model->removeColumn(column); + } + + template <typename I = Impl, if_canRemoveColumns<I> = true> + bool removeColumns(int column, int count) + { + return storage->removeColumns(column, count, {}); + } + + template <typename F = row_features, if_canMoveItems<F> = true> + bool moveColumn(int from, int to) + { + return moveColumns(from, 1, to); + } + + template <typename F = row_features, if_canMoveItems<F> = true> + bool moveColumns(int from, int count, int to) + { + return storage->moveColumns(storage.root(), from, count, storage.root(), to); + } + + template <typename I = Impl, typename F = row_features, + if_canMoveItems<F> = true, if_tree<I> = true> + bool moveColumn(QSpan<const int> source, int to) + { + const QModelIndex parent = this->index(source.first(source.size() - 1), 0); + return storage->moveColumns(parent, source.back(), 1, parent, to); + } + + template <typename I = Impl, typename F = row_features, + if_canMoveItems<F> = true, if_tree<I> = true> + bool moveColumns(QSpan<const int> source, int count, int destination) + { + const QModelIndex parent = this->index(source.first(source.size() - 1), 0); + return storage->moveColumns(parent, source.back(), count, parent, destination); + } + +private: + friend inline + bool comparesEqual(const QRangeModelAdapter &lhs, const QRangeModelAdapter &rhs) noexcept + { + return lhs.storage.m_model == rhs.storage.m_model; + } + Q_DECLARE_EQUALITY_COMPARABLE(QRangeModelAdapter) + + friend inline + bool comparesEqual(const QRangeModelAdapter &lhs, const range_type &rhs) + { + return lhs.range() == rhs; + } + Q_DECLARE_EQUALITY_COMPARABLE_NON_NOEXCEPT(QRangeModelAdapter, range_type) + + + void emitDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) + { + Q_EMIT storage.implementation()->dataChanged(topLeft, bottomRight, {}); + } + + void beginSetRangeImpl(Impl *impl, range_type *oldRange, qsizetype newLastRow) + { + const QModelIndex root = storage.root(); + const qsizetype oldLastRow = qsizetype(Impl::size(oldRange)) - 1; + + if (!root.isValid()) { + impl->beginResetModel(); + impl->deleteOwnedRows(); + } else if constexpr (is_tree<Impl>) { + if (oldLastRow > 0) { + impl->beginRemoveRows(root, 0, model()->rowCount(root) - 1); + impl->deleteRemovedRows(QRangeModelDetails::refTo(oldRange)); + impl->endRemoveRows(); + } + if (newLastRow > 0) + impl->beginInsertRows(root, 0, newLastRow); + } else { + Q_ASSERT_X(false, "QRangeModelAdapter::setRange", + "Internal error: The root index in a table or list must be invalid."); + } + } + + void endSetRangeImpl(Impl *impl, qsizetype newLastRow) + { + const QModelIndex root = storage.root(); + if (!root.isValid()) { + impl->endResetModel(); + } else if constexpr (is_tree<Impl>) { + if (newLastRow > 0) { + Q_ASSERT(model()->hasChildren(root)); + // if it was moved, then newRange is now likely to be empty. Get + // the inserted row. + impl->setParentRow(QRangeModelDetails::refTo(impl->childRange(root)), + QRangeModelDetails::pointerTo(impl->rowData(root))); + impl->endInsertRows(); + } + } + } + + template <typename Assigner> + void setRangeImpl(qsizetype newLastRow, Assigner &&assigner) + { + auto *impl = storage.implementation(); + auto *oldRange = impl->childRange(storage.root()); + beginSetRangeImpl(impl, oldRange, newLastRow); + assigner(QRangeModelDetails::refTo(oldRange)); + endSetRangeImpl(impl, newLastRow); + + if constexpr (Impl::itemsAreQObjects) { + if (model()->autoConnectPolicy() == QRangeModel::AutoConnectPolicy::Full) { + const auto begin = QRangeModelDetails::begin(QRangeModelDetails::refTo(oldRange)); + const auto end = QRangeModelDetails::end(QRangeModelDetails::refTo(oldRange)); + int rowIndex = 0; + for (auto it = begin; it != end; ++it, ++rowIndex) + impl->autoConnectPropertiesInRow(*it, rowIndex, storage.root()); + } + } + } + + template <typename P> + static auto setParentRow(P protocol, row_type &newRow, row_ptr parentRow) + -> decltype(protocol.setParentRow(std::declval<row_type&>(), std::declval<row_ptr>())) + { + return protocol.setParentRow(newRow, parentRow); + } + + template <typename ...Args> static constexpr void setParentRow(Args &&...) {} + + template <typename D> + bool insertRowImpl(int before, const QModelIndex &parent, D &&data) + { + return storage.implementation()->doInsertRows(before, 1, parent, [&data, this] + (range_type &range, auto parentRow, int row, int count) { + Q_UNUSED(this); + const auto oldSize = range.size(); + auto newRow = range.emplace(QRangeModelDetails::pos(range, row), std::forward<D>(data)); + setParentRow(storage.implementation()->protocol(), *newRow, parentRow); + return range.size() == oldSize + count; + }); + } + + template <typename LHS> + static auto selfInsertion(LHS *lhs, LHS *rhs) -> decltype(lhs == rhs) + { + if (lhs == rhs) { +#ifndef QT_NO_DEBUG + qCritical("Inserting data into itself is not supported"); +#endif + return true; + } + return false; + } + template <typename LHS, typename RHS> + static constexpr bool selfInsertion(LHS *, RHS *) { return false; } + + template <typename C> + bool insertRowsImpl(int before, const QModelIndex &parent, C &&data) + { + bool result = false; + result = storage->doInsertRows(before, int(std::size(data)), parent, [&data, this] + (range_type &range, auto parentRow, int row, int count){ + Q_UNUSED(parentRow); + Q_UNUSED(this); + const auto pos = QRangeModelDetails::pos(range, row); + const auto oldSize = range.size(); + + auto dataRange = [&data]{ + if constexpr (std::is_rvalue_reference_v<C&&>) { + return std::make_pair( + std::move_iterator(std::begin(data)), + std::move_iterator(std::end(data)) + ); + } else { + return std::make_pair(std::begin(data), std::end(data)); + } + }(); + + if constexpr (range_features::has_insert_range) { + if (selfInsertion(&range, &data)) + return false; + auto start = range.insert(pos, dataRange.first, dataRange.second); + if constexpr (protocol_traits::has_setParentRow) { + while (count) { + setParentRow(storage->protocol(), *start, parentRow); + ++start; + --count; + } + } else { + Q_UNUSED(start); + } + } else { + auto newRow = range.insert(pos, count, row_type{}); + while (dataRange.first != dataRange.second) { + *newRow = *dataRange.first; + setParentRow(storage->protocol(), *newRow, parentRow); + ++dataRange.first; + ++newRow; + } + } + return range.size() == oldSize + count; + }); + return result; + } + + template <typename D, typename = void> + struct DataFromList { + static constexpr auto first(D &data) { return &data; } + static constexpr auto next(D &, D *entry) { return entry; } + }; + + template <typename D> + struct DataFromList<D, std::enable_if_t<QRangeModelDetails::range_traits<D>::value>> + { + static constexpr auto first(D &data) { return std::begin(data); } + static constexpr auto next(D &data, typename D::iterator entry) + { + ++entry; + if (entry == std::end(data)) + entry = first(data); + return entry; + } + }; + + template <typename D, typename = void> struct RowFromTable + { + static constexpr auto first(D &data) { return &data; } + static constexpr auto next(D &, D *entry) { return entry; } + }; + + template <typename D> + struct RowFromTable<D, std::enable_if_t<std::conjunction_v< + QRangeModelDetails::range_traits<D>, + QRangeModelDetails::range_traits<typename D::value_type> + >> + > : DataFromList<D> + {}; + + template <typename D> + bool insertColumnImpl(int before, const QModelIndex &parent, D data) + { + auto entry = DataFromList<D>::first(data); + + return storage->doInsertColumns(before, 1, parent, [&entry, &data] + (auto &range, auto pos, int count) { + const auto oldSize = range.size(); + range.insert(pos, *entry); + entry = DataFromList<D>::next(data, entry); + return range.size() == oldSize + count; + }); + } + + template <typename C> + bool insertColumnsImpl(int before, const QModelIndex &parent, C data) + { + bool result = false; + auto entries = RowFromTable<C>::first(data); + auto begin = std::begin(*entries); + auto end = std::end(*entries); + result = storage->doInsertColumns(before, int(std::size(*entries)), parent, + [&begin, &end, &entries, &data](auto &range, auto pos, int count) { + const auto oldSize = range.size(); + if constexpr (row_features::has_insert_range) { + range.insert(pos, begin, end); + } else { + auto start = range.insert(pos, count, {}); + std::copy(begin, end, start); + } + entries = RowFromTable<C>::next(data, entries); + begin = std::begin(*entries); + end = std::end(*entries); + return range.size() == oldSize + count; + }); + return result; + } +}; + +template <typename Range, typename Protocol> +QRangeModelAdapter(Range &&, Protocol &&) -> QRangeModelAdapter<Range, Protocol>; + +template <typename Range> +QRangeModelAdapter(Range &&) -> QRangeModelAdapter<Range, void>; + +QT_END_NAMESPACE + +#endif // QRANGEMODELADAPTER_H diff --git a/src/corelib/itemmodels/qrangemodeladapter.qdoc b/src/corelib/itemmodels/qrangemodeladapter.qdoc new file mode 100644 index 00000000000..88872589299 --- /dev/null +++ b/src/corelib/itemmodels/qrangemodeladapter.qdoc @@ -0,0 +1,945 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only +// Qt-Security score:insignificant reason:default + +#include <QtCore/qrangemodeladapter.h> + +/*! + \class QRangeModelAdapter + \inmodule QtCore + \since 6.11 + \preliminary + \ingroup model-view + \brief QRangeModelAdapter provides QAbstractItemModel-compliant access to any C++ range. + \compares equality + \compareswith equality Range + \endcompareswith + + QRangeModelAdapter provides a type-safe and structure-aware C++ API around + a C++ range and a QRangeModel. Modifications made to the C++ range using + the adapter will inform clients of the QRangeModel about the changes. This + makes sure that item views are updated, caches are cleaned, and persistent + item indexes are invalidated and adapted correctly. + + \section1 Construction and model ownership + + QRangeModelAdapter has to be constructed from a C++ range. As with + QRangeModel, the range can be provided by lvalue or rvalue reference, as a + reference wrapper, and as a raw or smart pointer. + + \snippet qrangemodeladapter/main.cpp construct + + Constructing the adapter from a range implicitly constructs a QRangeModel + instance from that same range. Use model() to get it, and pass it to Qt + Widgets or Qt Quick item views as usual. + + \snippet qrangemodeladapter/main.cpp use-model + + The adapter owns the model. QRangeModelAdapter is a value type, so it can be + copied and moved. All copies share the same QRangeModel, which will be + destroyed when the last copy of the adapter is destroyed. + + If the adapter was created from an lvalue or rvalue reference, then the + adapter and model will operate on a copy of the original range object. + Otherwise, modifications made through the adapter or model will be written + to the original range object. To get the updated range, use the range() + function explicitly, or use the implicit conversion of the adapter to a + the range. + + \snippet qrangemodeladapter/main.cpp get-range + + To replace the entire range data with data from another (compatible) range, + use the setRange() function or the assignment operator. + + \snippet qrangemodeladapter/main.cpp set-range + + \section1 Accessing item data + + The QRangeModelAdapter API provides type-safe read and write access to the + range that the model operates on. The adapter API is based on the typical + API for C++ containers and ranges, including iterators. To access + individual rows and items, use at(), the corresponding subscript + \c{operator[]}, or data(). Which overloads of those functions are available + depends on the range for which the adapter was constructed. + + \section2 Reading item data as a QVariant + + The data() function always returns a QVariant with the value stored at the + specified position and role. In a list, an item can be accessed by a single + integer value specifying the row: + + \snippet qrangemodeladapter/main.cpp list-data + + If the range is a table, then items are specified by row and column: + + \snippet qrangemodeladapter/main.cpp table-data + + If the range is a tree, then items are located using a path of rows, and a + single column value: + + \snippet qrangemodeladapter/main.cpp tree-data + + Using a single integer as the row provides access to the toplevel tree + items. + + \snippet qrangemodeladapter/main.cpp multirole-data + + If no role is specified, then the QVariant will hold the entire item at the + position. Use the \l{QVariant::fromValue()} template function to retrieve a + copy of the item. + + \section2 Reading and writing using at() + + That the data() function returns a QVariant makes it flexible, but removes + type safety. For ranges where all items are of the same type, the at() + function provides a type-safe alternative that is more compatible with + regular C++ containers. As with data(), at() overloads exist to access an + item at a row for lists, at a row/column pair for table, and a path/column + pair for trees. However, at() always returns the whole item at the + specified position; it's not possible to read an individual role values for + an item. + + As expected from a C++ container API, the const overloads of at() (and the + corresponding subscript \c{operator[]}) provide immutable access to the + value, while the mutable overloads return a reference object that a new + value can be assigned to. Note that a QRangeModelAdapter operating on a + const range behaves in that respect like a const QRangeModelAdapter. The + mutable overloads are removed from the overload set, so the compiler will + always select the const version. Trying to call a function that modifies a + range will result in a compiler error, even if the adapter itself is + mutable: + + \snippet qrangemodeladapter/main.cpp read-only + + The returned reference objects are wrappers that convert implicitly to the + underlying type, have a \c{get()} function to explicitly access the + underlying value, and an \c{operator->()} that provides direct access to + const member functions. However, to prevent accidental data changes that + would bypass the QAbstractItemModel notification protocol, those reference + objects prevent direct modifications of the items. + + \note Accessing the reference object always makes a call to the model to get + a copy of the value. This can be expensive; for performance critical access + to data, store a copy. + + \section3 Item access + + If the range is represented as a list, then only the overloads taking a row + are available. + + \snippet qrangemodeladapter/main.cpp list-access + + The const overload returns the item at that row, while the mutable overload + returns a wrapper that implicitly converts to and from the value type of the + list. + + \snippet qrangemodeladapter/main.cpp list-access-multirole + + Assign a value to the wrapper to modify the data in the list. The model will + emit \l{QAbstractItemModel::}{dataChanged()} for all roles. + + When using the mutable overloads, you can also access the item type's const + members using the overloaded arrow operator. + + \snippet qrangemodeladapter/main.cpp list-access-multirole-member-access + + It is not possible to access non-const members of the item. Such + modifications would bypass the adapter, which couldn't notify the model + about the changes. To modify the value stored in the model, make a copy, + modify the properties of the copy, and then write that copy back. + + \snippet qrangemodeladapter/main.cpp list-access-multirole-write-back + + This will make the model emit \l{QAbstractItemModel::}{dataChanged()} for + this item, and for all roles. + + If the range is represented as a table, then you can access an individual + item by row and columns, using the \l{at(int, int)}{at(row, column)} + overload. For trees, that overload gives access to top-level items, while + the \l{at(QSpan<const int>, int)}{at(path, column)} overload provides + access to items nested within the tree. + + Accessing an individual item in a table or tree is equivalent to accessing + an item in a list. + + \snippet qrangemodeladapter/main.cpp table-item-access + + If the range doesn't store all columns using the same data type, then + \c{at(row,column)} returns a (a reference wrapper with a) QVariant holding + the item. + + \snippet qrangemodeladapter/main.cpp table-mixed-type-access + + \section2 Accessing rows in tables and trees + + For tables and trees, the overloads of at() and subscript \c{operator[]} + without the column parameter provide access to the entire row. The value + returned by the const overloads will be a reference type that gives access + to the row data. If that row holds pointers, then that reference type will + be a view of the row, giving access to pointers to const items. + + \section3 Table row access + + The \l{at(int)} overload is still available, but it returns the entire table + wor. This makes it possible to work with all the values in the row at once. + + \snippet qrangemodeladapter/main.cpp table-row-const-access + + As with items, the const overload provides direct access to the row type, + while the mutable overload returns a wrapper that acts as a reference to the + row. The wrapper provides access to const member functions using the + overloaded arrow operator. To modify the values, write a modified row type + back. + + \snippet qrangemodeladapter/main.cpp table-row-access + + When assigning a new value to the row, then the model emits the + \l{QAbstractItemModel::}{dataChanged()} signal for all items in the row, + and for all roles. + + \note When using a row type with runtime sizes, such as a \c{std::vector} or + a QList, make sure that the new row has the correct size. + + \section3 Tree row access + + Rows in trees are specified by a sequence of integers, one entry for each + level in the tree. Note that in the following snippets, the Tree is a range + holding raw row pointers, and that the adapter is created with an rvalue + reference of that range. This gives the QRangeModel ownership over the row + data. + + \snippet qrangemodeladapter/main.cpp tree-row-access + + The overload of \l{at(int)}{at()} taking a single row value provides + access to the top-level rows, or items in the top-level rows. + + \snippet qrangemodeladapter/main.cpp tree-item-access + + The basic pattern for accessing rows and items in a tree is identical to + accessing rows and items in a table. However, the adapter will make sure + that the tree structure is maintained when modifying entire rows. + + \snippet qrangemodeladapter/main.cpp tree-row-write + + In this example, a new row object is created, and assigned to the first + child of the first top-level item. + + \section1 Iterator API + + Use begin() and end() to get iterators over the rows of the model, or use + ranged-for. If the range is a list of items, dereferencing the iterator + will give access to item data. + + \snippet qrangemodeladapter/main.cpp ranged-for-const-list + + As with the const and mutable overloads of at(), a mutable iterator will + dereference to a wrapper. + + \snippet qrangemodeladapter/main.cpp ranged-for-mutable-list + + Use the overloaded arrow operator to access const members of the item type, + and assign to the wrapper to replace the value. + + It the range is a table or tree, then iterating over the model will give + access to the rows. + + \snippet qrangemodeladapter/main.cpp ranged-for-const-table + + Both the const and the mutable iterator will dereference to a wrapper type + for the row. This make sure that we can consistently iterate over each + column, even if the underlying row type is not a range (e.g. it might be a + tuple or gadget). + + \snippet qrangemodeladapter/main.cpp ranged-for-const-table-items + + When iterating over a mutable table we can overwrite the entire row. + + \snippet qrangemodeladapter/main.cpp ranged-for-mutable-table + + The model emits the \l{QAbstractItemModel::}{dataChanged()} signal for + all items in all row, and for all roles. + + \snippet qrangemodeladapter/main.cpp ranged-for-mutable-table-items + + Iterating over the mutable rows allows us to modify individual items. + + When iterating over a tree, the row wrapper has two additional member + functions, hasChildren() and children(), that allow us to traverse the + entire tree using iterators. + + \snippet qrangemodeladapter/main.cpp ranged-for-tree + + The object returned by children() is a QRangeModelAdapter operating on + the same model as the callee, but all operations will use the source row + index as the parent index. + + \sa QRangeModel +*/ + +/*! + \typedef QRangeModelAdapter::range_type +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> QRangeModelAdapter<Range, Protocol, Model>::QRangeModelAdapter(Range &&range, Protocol &&protocol) + \fn template <typename Range, typename Protocol, typename Model> QRangeModelAdapter<Range, Protocol, Model>::QRangeModelAdapter(Range &&range) + + Constructs a QRangeModelAdapter that operates on \a range. For tree ranges, + the optional \a protocol will be used for tree traversal. +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> bool QRangeModelAdapter<Range, Protocol, Model>::operator==(const QRangeModelAdapter &lhs, const QRangeModelAdapter &rhs) + + \return whether \a lhs is equal to \a rhs. Two adapters are equal if they + both hold the same \l{model()}{model} instance. +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> bool QRangeModelAdapter<Range, Protocol, Model>::operator!=(const QRangeModelAdapter &lhs, const QRangeModelAdapter &rhs) + + \return whether \a lhs is not equal to \a rhs. Two adapters are equal if + they both hold the same \l{model()}{model} instance. +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> Model *QRangeModelAdapter<Range, Protocol, Model>::model() const + + \return the QRangeModel instance created by this adapter. + + \sa range(), at() +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> const QRangeModelAdapter<Range, Protocol, Model>::range_type &QRangeModelAdapter<Range, Protocol, Model>::range() const + \fn template <typename Range, typename Protocol, typename Model> QRangeModelAdapter<Range, Protocol, Model>::operator const QRangeModelAdapter<Range, Protocol, Model>::range_type &() const + + \return a const reference to the range that the model adapter operates on. + + \sa setRange(), at(), model() +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> template <typename NewRange, QRangeModelAdapter<Range, Protocol, Model>::if_assignable_range<NewRange>> void QRangeModelAdapter<Range, Protocol, Model>::setRange(NewRange &&newRange) + \fn template <typename Range, typename Protocol, typename Model> template <typename NewRange, QRangeModelAdapter<Range, Protocol, Model>::if_assignable_range<NewRange>, QRangeModelAdapter<Range, Protocol, Model>::unless_adapter<NewRange>> QRangeModelAdapter &QRangeModelAdapter<Range, Protocol, Model>::operator=(NewRange &&newRange) + + Replaces the contents of the model with the rows in \a newRange, possibly + using move semantics. + + This function makes the model() emit the \l{QAbstractItemModel::}{modelAboutToBeReset()} + and \l{QAbstractItemModel::}{modelReset()} signals. + + \constraints \c Range is mutable, and \a newRange is of a type that can be + assigned to \c Range, but not a QRangeModelAdapter. + + \sa range(), at(), model(), assign() +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> template <typename Row, QRangeModelAdapter<Range, Protocol, Model>::if_assignable_range<std::initializer_list<Row>>> void QRangeModelAdapter<Range, Protocol, Model>::setRange(std::initializer_list<Row> newRange) + \fn template <typename Range, typename Protocol, typename Model> template <typename Row, QRangeModelAdapter<Range, Protocol, Model>::if_assignable_range<std::initializer_list<Row>>> QRangeModelAdapter &QRangeModelAdapter<Range, Protocol, Model>::assign(std::initializer_list<Row> newRange) + \fn template <typename Range, typename Protocol, typename Model> template <typename Row, QRangeModelAdapter<Range, Protocol, Model>::if_assignable_range<std::initializer_list<Row>>> QRangeModelAdapter &QRangeModelAdapter<Range, Protocol, Model>::operator=(std::initializer_list<Row> newRange) + + Replaces the contents of the model with the rows in \a newRange. + + \constraints \c Range is mutable, and \a newRange can be assigned to \c Range. + + \sa range(), at, model() +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> template <typename InputIterator, typename Sentinel, typename I = Impl, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> void QRangeModelAdapter<Range, Protocol, Model>::setRange(InputIterator first, Sentinel last) + \fn template <typename Range, typename Protocol, typename Model> template <typename InputIterator, typename Sentinel, typename I = Impl, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> void QRangeModelAdapter<Range, Protocol, Model>::assign(InputIterator first, Sentinel last) + + Replaces the contents of the models with the rows in the range [\a first, \a last). + + \sa range(), at, model() +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_list<I>> QModelIndex QRangeModelAdapter<Range, Protocol, Model>::index(int row) const + \overload + + Returns the QModelIndex for \a row. + + \constraints \c Range is a one-dimensional list. +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::unless_list<I>> QModelIndex QRangeModelAdapter<Range, Protocol, Model>::index(int row, int column) const + \overload + + Returns the QModelIndex for the item at \a row, \a column. + + \constraints \c Range is a table or tree. +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> QModelIndex QRangeModelAdapter<Range, Protocol, Model>::index(QSpan<const int> path, int column) const + \overload + + Returns the QModelIndex for the item at \a column for the row in the tree + specified by \a path. + + \constraints \c Range is a tree. +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> int QRangeModelAdapter<Range, Protocol, Model>::columnCount() const + + \return the number of columns. This will be one if the \c Range represents a + list, otherwise this returns be the number of elements in each row. +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> int QRangeModelAdapter<Range, Protocol, Model>::rowCount() const + \overload + + \return the number of rows. If the \c Range represents a list or table, then + this is the number of rows. For trees, this is the number of top-level rows. +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> int QRangeModelAdapter<Range, Protocol, Model>::rowCount(int row) const + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> int QRangeModelAdapter<Range, Protocol, Model>::rowCount(QSpan<const int> row) const + \overload + + \return the number of rows under \a row. + \constraints \c Range is a tree. +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> int QRangeModelAdapter<Range, Protocol, Model>::hasChildren(int row) const + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> int QRangeModelAdapter<Range, Protocol, Model>::hasChildren(QSpan<const int> row) const + \overload + + \return whether there are any rows under \a row. + \constraints \c Range is a tree. +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_list<I>> QVariant QRangeModelAdapter<Range, Protocol, Model>::data(int row) const + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_list<I>> QVariant QRangeModelAdapter<Range, Protocol, Model>::data(int row, int role) const + + \return a QVariant holding the data stored under the given \a role for the + item at \a row, or an invalid QVariant if there is no item. If \a role is + not specified, then returns a QVariant holding the complete item. + + \constraints \c Range is a list. + + \sa setData(), at() +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_list<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> bool QRangeModelAdapter<Range, Protocol, Model>::setData(int row, const QVariant &value, int role) + + Sets the \a role data for the item at \a row to \a value. + + Returns \c{true} if successful; otherwise returns \c{false}. + + \constraints \c Range is a mutable list. + + \sa data(), at() +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::unless_list<I>> QVariant QRangeModelAdapter<Range, Protocol, Model>::data(int row, int column) const + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::unless_list<I>> QVariant QRangeModelAdapter<Range, Protocol, Model>::data(int row, int column, int role) const + + \return a QVariant holding the data stored under the given \a role for the + item referred to by a \a row and \a column, or an invalid QVariant if there + is no data stored for that position or role. If \a role is not specified, + then returns a QVariant holding the complete item. + + \constraints \c Range is a table or tree. + + \sa setData(), at() +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::unless_list<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> bool QRangeModelAdapter<Range, Protocol, Model>::setData(int row, int column, const QVariant &value, int role) + + Sets the \a role data for the item referred to by \a row and \a column to + \a value. + + Returns \c{true} if successful; otherwise returns \c{false}. + + \constraints \c Range is mutable, and not a list. + + \sa data(), at() +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> QVariant QRangeModelAdapter<Range, Protocol, Model>::data(QSpan<const int> path, int column) const + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> QVariant QRangeModelAdapter<Range, Protocol, Model>::data(QSpan<const int> path, int column, int role) const + + \return a QVariant holding the data stored under the given \a role for the + item referred to by \a path and \a column, or an invalid QVariant if there + is no data stored for that position or role. If \a role is not specified, + then returns a QVariant holding the complete item. + + \constraints \c Range is a tree. + + \sa setData(), at() +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> bool QRangeModelAdapter<Range, Protocol, Model>::setData(QSpan<const int> path, int column, const QVariant &value, int role) + + Sets the \a role data for the item referred to by \a path and \a column to + \a value. + + Returns \c{true} if successful; otherwise returns \c{false}. + + \constraints \c Range is a mutable tree. + + \sa data(), at() +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_list<I>> auto QRangeModelAdapter<Range, Protocol, Model>::at(int row) const + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_list<I>> auto QRangeModelAdapter<Range, Protocol, Model>::operator[](int row) const + + \return the value at \a row as the type stored in \c Range. + + \constraints \c Range is a list. +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_list<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> auto QRangeModelAdapter<Range, Protocol, Model>::at(int row) + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_list<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> auto QRangeModelAdapter<Range, Protocol, Model>::operator[](int row) + + \return the value at \a row wrapped into a mutable reference to the type + stored in \c Range. + +//! [data-ref] + \note Modifications to the range will invalidate that reference. To modify + the reference, assign a new value to it. Unless the value stored in the + \c Range is a pointer, it is not possible to access individual members of + the stored value. +//! [data-ref] + + \constraints \c Range is a mutable list. +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::unless_list<I>> decltype(auto) QRangeModelAdapter<Range, Protocol, Model>::at(int row) const + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::unless_list<I>> decltype(auto) QRangeModelAdapter<Range, Protocol, Model>::operator[](int row) const + + \return a constant reference to the row at \a row, as stored in \c Range. + + \constraints \c Range is a table or tree. +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_table<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> auto QRangeModelAdapter<Range, Protocol, Model>::at(int row) + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_table<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> auto QRangeModelAdapter<Range, Protocol, Model>::operator[](int row) + + \return a mutable reference to the row at \a row, as stored in \c Range. + + \constraints \c Range is a mutable table, but not a tree. +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> auto QRangeModelAdapter<Range, Protocol, Model>::at(int row) + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> auto QRangeModelAdapter<Range, Protocol, Model>::operator[](int row) + + \return a mutable wrapper holding a reference to the tree row specified by \a row. + +//! [treerow-ref] + To modify the tree row, assign a new value to it. Assigning a new tree row + will set the parent the new tree row to be the parent of the old tree row. + However, neither the old nor the new tree row must have any child rows. To + access the tree row, dereferencing the wrapper using \c{operator*()}, or use + \c{operator->()} to access tree row members. + + \note Modifications to the range will invalidate the wrapper. +//! [treerow-ref] + + \constraints \c Range is a tree. +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::unless_list<I>> auto QRangeModelAdapter<Range, Protocol, Model>::at(int row, int column) const +\omit + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::unless_list<I>> auto QRangeModelAdapter<Range, Protocol, Model>::operator[](int row, int column) const +\endomit + + \return a copy of the value stored as the item specified by \a row and + \a column. If the item is a multi-role item, then this returns a copy of + the entire item. If the rows in the \c Range store different types at + different columns, then the return type will be a QVariant. + + \constraints \c Range is a table or tree. +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::unless_list<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> auto QRangeModelAdapter<Range, Protocol, Model>::at(int row, int column) +\omit + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::unless_list<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> auto QRangeModelAdapter<Range, Protocol, Model>::operator[](int row, int column) +\endomit + + \return a mutable reference to the value stored as the item specified by + \a row and \a column. If the item is a multi-role item, then this will be + a reference to the entire item. + + \include qrangemodeladapter.qdoc data-ref + + \constraints \c Range is a mutable table. +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> decltype(auto) QRangeModelAdapter<Range, Protocol, Model>::at(QSpan<const int> path) const + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> decltype(auto) QRangeModelAdapter<Range, Protocol, Model>::operator[](QSpan<const int> path) const + + \return a constant reference to the row specified by \a path, as stored in + \c Range. + + \constraints \c Range is a tree. +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> auto QRangeModelAdapter<Range, Protocol, Model>::at(QSpan<const int> path) + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> auto QRangeModelAdapter<Range, Protocol, Model>::operator[](QSpan<const int> path) + + \return a mutable wrapper holding a reference to the tree row specified by \a path. + + \include qrangemodeladapter.qdoc treerow-ref + + \constraints \c Range is a tree. +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> auto QRangeModelAdapter<Range, Protocol, Model>::at(QSpan<const int> path, int column) const +\omit + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> auto QRangeModelAdapter<Range, Protocol, Model>::operator[](QSpan<const int> path, int column) const +\endomit + + \return a copy of the value stored as the item specified by \a path and + \a column. If the item is a multi-role item, then this returns a copy of + the entire item. If the rows in the \c Range store different types at + different columns, then the return type will be a QVariant. + + \constraints \c Range is a tree. +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> auto QRangeModelAdapter<Range, Protocol, Model>::at(QSpan<const int> path, int column) +\omit + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> auto QRangeModelAdapter<Range, Protocol, Model>::operator[](QSpan<const int> path, int column) +\endomit + + \return a mutable reference to the value stored as the item specified by + \a path and \a column. If the item is a multi-role item, then this will be + a reference to the entire item. If the rows in the \c Range store different + types at different columns, then the return type will be a QVariant. + + \include qrangemodeladapter.qdoc data-ref + + \constraints \c Range is a mutable tree. +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_canInsertRows<I>> bool QRangeModelAdapter<Range, Protocol, Model>::insertRow(int before) + \overload + + Inserts a single empty row before the row at \a before, and returns whether + the insertion was successful. +//! [insert-row-appends] + If \a before is the same value as rowCount(), then the new row will be appended. +//! [insert-row-appends] + + \constraints \c Range supports insertion of elements. + + \sa insertRows(), removeRow(), insertColumn() +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_canInsertRows<I>, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> bool QRangeModelAdapter<Range, Protocol, Model>::insertRow(QSpan<const int> before) + \overload + + Inserts a single empty row before the row at the path specified by \a before, + and returns whether the insertion was successful. + \include qrangemodeladapter.qdoc insert-row-appends + + \constraints \c Range is a tree that supports insertion of elements. + + \sa insertRows(), removeRow(), insertColumn() +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> template <typename D, typename I, QRangeModelAdapter<Range, Protocol, Model>::if_canInsertRows<I>, QRangeModelAdapter<Range, Protocol, Model>::if_compatible_row<D>> bool QRangeModelAdapter<Range, Protocol, Model>::insertRow(int before, D &&data) + \overload + + Inserts a single row constructed from \a data before the row at \a before, + and returns whether the insertion was successful. + \include qrangemodeladapter.qdoc insert-row-appends + + \constraints \c Range supports insertion of elements, and if + a row can be constructed from \a data. + + \sa insertRows(), removeRow(), insertColumn() +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> template <typename D, typename I, QRangeModelAdapter<Range, Protocol, Model>::if_canInsertRows<I>, QRangeModelAdapter<Range, Protocol, Model>::if_compatible_row<D>, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> bool QRangeModelAdapter<Range, Protocol, Model>::insertRow(QSpan<const int> before, D &&data) + \overload + + Inserts a single row constructed from \a data before the row at \a before, + and returns whether the insertion was successful. + \include qrangemodeladapter.qdoc insert-row-appends + + \constraints \c Range is a tree that supports insertion of elements, and if + a row can be constructed from \a data. + + \sa insertRows(), removeRow(), insertColumn() +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> template <typename C, typename I, QRangeModelAdapter<Range, Protocol, Model>::if_canInsertRows<I>, QRangeModelAdapter<Range, Protocol, Model>::if_compatible_row_range<C>> bool QRangeModelAdapter<Range, Protocol, Model>::insertRows(int before, C &&data) + \overload + + Inserts rows constructed from the elements in \a data before the row at + \a before, and returns whether the insertion was successful. + \include qrangemodeladapter.qdoc insert-row-appends + + \constraints \c Range supports insertion of elemnets, and if + rows can be constructed from the elements in \a data. + + \sa insertRow(), removeRows(), insertColumns() +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> template <typename C, typename I, QRangeModelAdapter<Range, Protocol, Model>::if_canInsertRows<I>, QRangeModelAdapter<Range, Protocol, Model>::if_compatible_row_range<C>, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> bool QRangeModelAdapter<Range, Protocol, Model>::insertRows(QSpan<const int> before, C &&data) + \overload + + Inserts rows constructed from the elements in \a data before the row at + \a before, and returns whether the insertion was successful. + \include qrangemodeladapter.qdoc insert-row-appends + + \constraints \c Range is a tree that supports insertion of elements, and if + rows can be constructed from the elements in \a data. + + \sa insertRow(), removeRows(), insertColumns() +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_canRemoveRows<I>> bool QRangeModelAdapter<Range, Protocol, Model>::removeRow(int row) + \overload + + Removes the given \a row and returns whether the removal was successful. + + \constraints \c Range supports the removal of elements. + + \sa removeRows(), removeColumn(), insertRow() +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_canRemoveRows<I>, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> bool QRangeModelAdapter<Range, Protocol, Model>::removeRow(QSpan<const int> path) + \overload + + Removes the row at the given \a path, including all children of that row, + and returns whether the removal was successful. + + \constraints \c Range is a tree that supports the removal of elements. + + \sa removeRows(), removeColumn(), insertRow() +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_canRemoveRows<I>> bool QRangeModelAdapter<Range, Protocol, Model>::removeRows(int row, int count) + \overload + + Removes \a count rows starting at \a row, and returns whether the removal + was successful. + + \constraints \c Range supports the removal of elements. + + \sa removeRow(), removeColumns(), insertRows() +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_canRemoveRows<I>, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> bool QRangeModelAdapter<Range, Protocol, Model>::removeRows(QSpan<const int> path, int count) + \overload + + Removes \a count rows starting at the row specified by \a path, and returns + whether the removal was successful. + + \constraints \c Range is a tree that supports the removal of elements. + + \sa removeRow(), removeColumns(), insertRows() +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> template <typename F, QRangeModelAdapter<Range, Protocol, Model>::if_canMoveItems<F>> bool QRangeModelAdapter<Range, Protocol, Model>::moveRow(int source, int destination) + \overload + + Moves the row at \a source to the position at \a destination, and returns + whether the row was successfully moved. + + \constraints \c Range supports moving of elements. + + \sa insertRow(), removeRow(), moveColumn() +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> template <typename I, typename F, QRangeModelAdapter<Range, Protocol, Model>::if_canMoveItems<F>, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> bool QRangeModelAdapter<Range, Protocol, Model>::moveRow(QSpan<const int> source, QSpan<const int> destination) + \overload + + Moves the tree branch at \a source to the position at \a destination, and + returns whether the branch was successfully moved. + + \constraints \c Range is a tree that supports moving of elements. + + \sa insertRow(), removeRow(), moveColumn() +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> template <typename F, QRangeModelAdapter<Range, Protocol, Model>::if_canMoveItems<F>> bool QRangeModelAdapter<Range, Protocol, Model>::moveRows(int source, int count, int destination) + \overload + + Moves \a count rows starting at \a source to the position at \a destination, + and returns whether the rows were successfully moved. + + \constraints \c Range supports moving of elements. + + \sa insertRows(), removeRows(), moveColumns() +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> template <typename I, typename F, QRangeModelAdapter<Range, Protocol, Model>::if_canMoveItems<F>, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> bool QRangeModelAdapter<Range, Protocol, Model>::moveRows(QSpan<const int> source, int count, QSpan<const int> destination) + \overload + + Moves \a count tree branches starting at \a source to the position at + \a destination, and returns whether the rows were successfully moved. + + \constraints \c Range is a tree that supports moving of elements. + + \sa insertRows(), removeRows(), moveColumns() +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_canInsertColumns<I>> bool QRangeModelAdapter<Range, Protocol, Model>::insertColumn(int before) + \overload + + Inserts a single empty column before the column specified by \a before into + all rows, and returns whether the insertion was successful. +//! [insert-column-appends] + If \a before is the same value as columnCount(), then the column will be + appended to each row. +//! [insert-column-appends] + + \constraints \c Range has rows that support insertion of elements. + + \sa removeColumn(), insertColumns(), insertRow() +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> template <typename D, typename I, QRangeModelAdapter<Range, Protocol, Model>::if_canInsertColumns<I>, QRangeModelAdapter<Range, Protocol, Model>::if_compatible_column_data<D>> bool QRangeModelAdapter<Range, Protocol, Model>::insertColumn(int before, D &&data) + \overload + + Inserts a single column constructed from \a data before the column specified + by \a before into all rows, and returns whether the insertion was successful. +//! [insert-column-appends] + If \a before is the same value as columnCount(), then the column will be + appended to each row. +//! [insert-column-appends] + + If \a data is a single value, then the new entry in all rows will be constructed + from that single value. + + If \a data is a container, then the elements in that container will be used + sequentially to construct the column for each subsequent row. If there are + fewer elements in \a data than there are rows, then function wraps around + and starts again from the first element. + + \code + \endcode + + \constraints \c Range has rows that support insertion of elements, and the + elements can be constructed from the entries in \a data. + + \sa removeColumn(), insertColumns(), insertRow() +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> template <typename C, typename I, QRangeModelAdapter<Range, Protocol, Model>::if_canInsertColumns<I>, QRangeModelAdapter<Range, Protocol, Model>::if_compatible_column_range<C>> bool QRangeModelAdapter<Range, Protocol, Model>::insertColumns(int before, C &&data) + + Inserts columns constructed from the elements in \a data before the column + specified by \a before into all rows, and returns whether the insertion was + successful. +//! [insert-column-appends] + If \a before is the same value as columnCount(), then the column will be + appended to each row. +//! [insert-column-appends] + + If the elements in \a data are values, then the new entries in all rows will + be constructed from those values. + + If the elements in \a data are containers, then the entries in the outer + container will be used sequentially to construct the new entries for each + subsequent row. If there are fewer elements in \a data than there are rows, + then the function wraps around and starts again from the first element. + + \code + \endcode + + \constraints \c Range has rows that support insertion of elements, and the + elements can be constructed from the entries in \a data. + + \sa removeColumns(), insertColumn(), insertRows() +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_canRemoveColumns<I>> bool QRangeModelAdapter<Range, Protocol, Model>::removeColumn(int column) + + Removes the given \a column from each row, and returns whether the removal + was successful. + + \constraints \c Range has rows that support removal of elements. + + \sa insertColumn(), removeColumns(), removeRow() +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_canRemoveColumns<I>> bool QRangeModelAdapter<Range, Protocol, Model>::removeColumns(int column, int count) + + Removes \a count columns starting by the given \a column from each row, and + returns whether the removal was successful. + + \constraints \c Range has rows that support removal of elements. + + \sa insertColumns(), removeColumn(), removeRow() +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> template <typename F, QRangeModelAdapter<Range, Protocol, Model>::if_canMoveItems<F>> bool QRangeModelAdapter<Range, Protocol, Model>::moveColumn(int from, int to) + + Moves the column at \a from to the column at \a to, and returns whether the + column was successfully moved. + + \constraints \c Range has rows that support moving of elements. + + \sa insertColumn(), removeColumn(), moveColumns(), moveRow() +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> template <typename F, QRangeModelAdapter<Range, Protocol, Model>::if_canMoveItems<F>> bool QRangeModelAdapter<Range, Protocol, Model>::moveColumns(int from, int count, int to) + + Moves \a count columns starting at \a from to the position at \a to, and + returns whether the columns were successfully moved. + + \constraints \c Range has rows that support moving of elements. + + \sa insertColumns(), removeColumns(), moveColumn(), moveRows() +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> template <typename I, typename F, QRangeModelAdapter<Range, Protocol, Model>::if_canMoveItems<F>, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> bool QRangeModelAdapter<Range, Protocol, Model>::moveColumn(QSpan<const int> source, int to) + \internal Not possible to create a tree from a row type that can rotate/splice? +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> template <typename I, typename F, QRangeModelAdapter<Range, Protocol, Model>::if_canMoveItems<F>, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> bool QRangeModelAdapter<Range, Protocol, Model>::moveColumns(QSpan<const int> source, int count, int to) + \internal Not possible to create a tree from a row type that can rotate/splice? +*/ diff --git a/src/corelib/itemmodels/qrangemodeladapter_impl.h b/src/corelib/itemmodels/qrangemodeladapter_impl.h new file mode 100644 index 00000000000..ad1dd152b22 --- /dev/null +++ b/src/corelib/itemmodels/qrangemodeladapter_impl.h @@ -0,0 +1,402 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default + +#ifndef QRANGEMODELADAPTER_IMPL_H +#define QRANGEMODELADAPTER_IMPL_H + +#ifndef Q_QDOC + +#ifndef QRANGEMODELADAPTER_H +#error Do not include qrangemodeladapter_impl.h directly +#endif + +#if 0 +#pragma qt_sync_skip_header_check +#pragma qt_sync_stop_processing +#endif + +#include <QtCore/qrangemodel.h> +#include <QtCore/qspan.h> +#include <set> +#include <memory> + +QT_BEGIN_NAMESPACE + +namespace QRangeModelDetails +{ +template <typename Range, typename Protocol> +using RangeImplementation = std::conditional_t<std::is_void_v<Protocol>, + std::conditional_t<is_tree_range<Range>::value, + QGenericTreeItemModelImpl<Range, DefaultTreeProtocol<Range>>, + QGenericTableItemModelImpl<Range> + >, + QGenericTreeItemModelImpl<Range, Protocol> + >; + +// we can't use wrapped_t, we only want to unpack smart pointers, and maintain +// the pointer nature of the type. +template <typename T, typename = void> +struct data_type { using type = T; }; +template <> +struct data_type<void> { using type = QVariant; }; + +// pointer types of iterators use QtPrivate::ArrowProxy if the type does not +// provide operator->() (or is a pointer). +template <typename T, typename = void> struct test_pointerAccess : std::false_type {}; +template <typename T> struct test_pointerAccess<T *> : std::true_type {}; +template <typename T> +struct test_pointerAccess<T, std::void_t<decltype(std::declval<T>().operator->())>> + : std::true_type +{}; + +template <typename T> +using data_pointer_t = std::conditional_t<test_pointerAccess<T>::value, + T, QtPrivate::ArrowProxy<T>>; + +// Helpers to make a type const "in depth", taking into account raw pointers +// and wrapping types, like smart pointers and std::reference_wrapper. + +// We need to return data by value, not by reference, as we might only have +// temporary values (i.e. a QVariant returned by QAIM::data). +template <typename T, typename = void> struct AsConstData { using type = T; }; +template <typename T> struct AsConstData<const T &> { using type = T; }; +template <typename T> struct AsConstData<T *> { using type = const T *; }; +template <template <typename> typename U, typename T> +struct AsConstData<U<T>, std::enable_if_t<is_any_shared_ptr<U<T>>::value>> +{ using type = U<const T>; }; +template <typename T> struct AsConstData<std::reference_wrapper<T>> +{ using type = std::reference_wrapper<const T>; }; + +template <typename T> using asConst_t = typename AsConstData<T>::type; + +// Rows get wrapped into a "view", as a begin/end iterator/sentinel pair. +// The iterator dereferences to the const version of the value returned by +// the underlying iterator. +// Could be replaced with std::views::sub_range in C++ 20. +template <typename const_row_type, typename Iterator, typename Sentinel> +struct RowView +{ + // this is similar to C++23's std::basic_const_iterator, but we don't want + // to convert to the underlying const_iterator. + struct iterator + { + using value_type = asConst_t<typename Iterator::value_type>; + using difference_type = typename Iterator::difference_type; + using pointer = QRangeModelDetails::data_pointer_t<value_type>; + using reference = value_type; + using const_reference = value_type; + using iterator_category = typename std::iterator_traits<Iterator>::iterator_category; + + template <typename I, typename Category> + static constexpr bool is_atLeast = std::is_base_of_v<Category, + typename std::iterator_traits<I>::iterator_category>; + template <typename I, typename Category> + using if_atLeast = std::enable_if_t<is_atLeast<I, Category>, bool>; + + reference operator*() const { return *m_it; } + pointer operator->() const { return operator*(); } + + // QRM requires at least forward_iterator, so we provide both post- and + // prefix increment unconditionally + friend constexpr iterator &operator++(iterator &it) + noexcept(noexcept(++std::declval<Iterator&>())) + { + ++it.m_it; + return it; + } + friend constexpr iterator operator++(iterator &it, int) + noexcept(noexcept(std::declval<Iterator&>()++)) + { + iterator copy = it; + ++copy.m_it; + return copy; + } + + template <typename I = Iterator, if_atLeast<I, std::bidirectional_iterator_tag> = true> + friend constexpr iterator &operator--(iterator &it) + noexcept(noexcept(--std::declval<I&>())) + { + --it.m_it; + return it; + } + template <typename I = Iterator, if_atLeast<I, std::bidirectional_iterator_tag> = true> + friend constexpr iterator operator--(iterator &it, int) + noexcept(noexcept(std::declval<I&>()--)) + { + iterator copy = it; + --it.m_it; + return copy; + } + + template <typename I = Iterator, if_atLeast<I, std::random_access_iterator_tag> = true> + friend constexpr iterator &operator+=(iterator &it, difference_type n) + noexcept(noexcept(std::declval<I&>() += 1)) + { + it.m_it += n; + return it; + } + template <typename I = Iterator, if_atLeast<I, std::random_access_iterator_tag> = true> + friend constexpr iterator &operator-=(iterator &it, difference_type n) + noexcept(noexcept(std::declval<I&>() -= 1)) + { + it.m_it -= n; + return it; + } + + template <typename I = Iterator, if_atLeast<I, std::random_access_iterator_tag> = true> + friend constexpr iterator operator+(const iterator &it, difference_type n) + noexcept(noexcept(std::declval<I&>() + 1)) + { + iterator copy = it; + copy.m_it += n; + return copy; + } + template <typename I = Iterator, if_atLeast<I, std::random_access_iterator_tag> = true> + friend constexpr iterator operator+(difference_type n, const iterator &it) + noexcept(noexcept(1 + std::declval<I&>())) + { + return it + n; + } + template <typename I = Iterator, if_atLeast<I, std::random_access_iterator_tag> = true> + friend constexpr iterator operator-(const iterator &it, difference_type n) + noexcept(noexcept(std::declval<I&>() - 1)) + { + iterator copy = it; + copy.m_it = it.m_it - n; + return copy; + } + + template <typename I = Iterator, if_atLeast<I, std::random_access_iterator_tag> = true> + constexpr reference operator[](difference_type n) const + noexcept(noexcept(I::operator[]())) + { + return m_it[n]; + } + + template <typename I = Iterator, if_atLeast<I, std::random_access_iterator_tag> = true> + friend constexpr difference_type operator-(const iterator &lhs, const iterator &rhs) + noexcept(noexcept(std::declval<I&>() - std::declval<I&>())) + { + return lhs.m_it - rhs.m_it; + } + + template <typename I = Iterator, if_atLeast<I, std::random_access_iterator_tag> = true> + friend constexpr bool operator<(const iterator &lhs, const iterator &rhs) + noexcept(noexcept(std::declval<I&>() < std::declval<I&>())) + { + return lhs.m_it < rhs.m_it; + } + template <typename I = Iterator, if_atLeast<I, std::random_access_iterator_tag> = true> + friend constexpr bool operator<=(const iterator &lhs, const iterator &rhs) + noexcept(noexcept(std::declval<I&>() <= std::declval<I&>())) + { + return lhs.m_it <= rhs.m_it; + } + + template <typename I = Iterator, if_atLeast<I, std::random_access_iterator_tag> = true> + friend constexpr bool operator>(const iterator &lhs, const iterator &rhs) + noexcept(noexcept(std::declval<I&>() > std::declval<I&>())) + { + return lhs.m_it > rhs.m_it; + } + template <typename I = Iterator, if_atLeast<I, std::random_access_iterator_tag> = true> + friend constexpr bool operator>=(const iterator &lhs, const iterator &rhs) + noexcept(noexcept(std::declval<I&>() >= std::declval<I&>())) + { + return lhs.m_it >= rhs.m_it; + } + + // This would implement the P2836R1 fix from std::basic_const_iterator, + // but a const_iterator on a range<pointer> would again allow us to + // mutate the pointed-to object, which is exactly what we want to + // prevent. + /* + template <typename CI, std::enable_if_t<std::is_convertible_v<const Iterator &, CI>, bool> = true> + operator CI() const + { + return CI{m_it}; + } + + template <typename CI, std::enable_if_t<std::is_convertible_v<Iterator, CI>, bool> = true> + operator CI() && + { + return CI{std::move(m_it)}; + } + */ + + friend bool comparesEqual(const iterator &lhs, const iterator &rhs) noexcept + { + return lhs.m_it == rhs.m_it; + } + Q_DECLARE_EQUALITY_COMPARABLE(iterator) + + Iterator m_it; + }; + + using value_type = typename iterator::value_type; + using difference_type = typename iterator::difference_type; + + friend bool comparesEqual(const RowView &lhs, const RowView &rhs) noexcept + { + return lhs.m_begin == rhs.m_begin; + } + Q_DECLARE_EQUALITY_COMPARABLE(RowView) + + template <typename RHS> + bool operator==(const RHS &rhs) const noexcept + { + return m_begin == QRangeModelDetails::begin(rhs); + } + template <typename RHS> + bool operator!=(const RHS &rhs) const noexcept + { + return !operator==(rhs); + } + + value_type at(difference_type n) const { return *std::next(m_begin, n); } + + iterator begin() const { return iterator{m_begin}; } + iterator end() const { return iterator{m_end}; } + + Iterator m_begin; + Sentinel m_end; +}; + +// Const-in-depth mapping for row types. We do store row types, and they might +// be move-only, so we return them by const reference. +template <typename T, typename = void> struct AsConstRow { using type = const T &; }; +// Otherwise the mapping for basic row types is the same as for data. +template <typename T> struct AsConstRow<T *> : AsConstData<T *> {}; +template <template <typename> typename U, typename T> +struct AsConstRow<U<T>, std::enable_if_t<is_any_shared_ptr<U<T>>::value>> : AsConstData<U<T>> {}; +template <typename T> struct AsConstRow<std::reference_wrapper<T>> + : AsConstData<std::reference_wrapper<T>> {}; + +template <typename T> using if_range = std::enable_if_t<is_range_v<T>, bool>; +// If the row type is a range, then we assume that the first type is the +// element type. +template <template <typename, typename ...> typename R, typename T, typename ...Args> +struct AsConstRow<R<T, Args...>, if_range<R<T, Args...>>> +{ + using type = const R<T, Args...> &; +}; + +// specialize for range of pointers and smart pointers +template <template <typename, typename ...> typename R, typename T, typename ...Args> +struct AsConstRow<R<T *, Args...>, if_range<R<T *, Args...>>> +{ + using row_type = R<T, Args...>; + using const_iterator = typename row_type::const_iterator; + using const_row_type = R<asConst_t<T>>; + using type = RowView<const_row_type, const_iterator, const_iterator>; +}; + +template <template <typename, typename ...> typename R, typename T, typename ...Args> +struct AsConstRow<R<T, Args...>, + std::enable_if_t<std::conjunction_v<is_range<R<T, Args...>>, is_any_shared_ptr<T>>> +> +{ + using row_type = R<T, Args...>; + using const_iterator = typename row_type::const_iterator; + using const_row_type = R<asConst_t<T>>; + using type = RowView<const_row_type, const_iterator, const_iterator>; +}; + +template <typename T> +using asConstRow_t = typename AsConstRow<T>::type; + +Q_CORE_EXPORT QVariant qVariantAtIndex(const QModelIndex &index); + +template <typename Type> +static inline Type dataAtIndex(const QModelIndex &index) +{ + Q_ASSERT_X(index.isValid(), "QRangeModelAdapter::dataAtIndex", "Index at position is invalid"); + QVariant variant = qVariantAtIndex(index); + + if constexpr (std::is_same_v<QVariant, Type>) + return variant; + else + return variant.value<Type>(); +} + +template <typename Type> +static inline Type dataAtIndex(const QModelIndex &index, int role) +{ + Q_ASSERT_X(index.isValid(), "QRangeModelAdapter::dataAtIndex", "Index at position is invalid"); + QVariant variant = index.data(role); + + if constexpr (std::is_same_v<QVariant, Type>) + return variant; + else + return variant.value<Type>(); +} + +template <bool isTree = false> +struct ParentIndex +{ + ParentIndex(const QModelIndex &dummy = {}) { Q_ASSERT(!dummy.isValid()); } + QModelIndex root() const { return {}; } +}; + +template <> +struct ParentIndex<true> +{ + const QModelIndex m_rootIndex; + QModelIndex root() const { return m_rootIndex; } +}; + +template <typename Model, typename Impl> +struct AdapterStorage : ParentIndex<Impl::protocol_traits::is_tree> +{ + // If it is, then we can shortcut the model and operate on the container. + // Otherwise we have to go through the model's vtable. For now, this is always + // the case. + static constexpr bool isRangeModel = std::is_same_v<Model, QRangeModel>; + static_assert(isRangeModel, "The model must be a QRangeModel (not a subclass)."); + std::shared_ptr<QRangeModel> m_model; + + template <typename I = Impl, std::enable_if_t<I::protocol_traits::is_tree, bool> = true> + explicit AdapterStorage(const std::shared_ptr<QRangeModel> &model, const QModelIndex &root) + : ParentIndex<Impl::protocol_traits::is_tree>{root}, m_model(model) + { + } + + explicit AdapterStorage(Model *model) + : m_model{model} + {} + + const Impl *implementation() const + { + return static_cast<const Impl *>(QRangeModelImplBase::getImplementation(m_model.get())); + } + + Impl *implementation() + { + return static_cast<Impl *>(QRangeModelImplBase::getImplementation(m_model.get())); + } + + auto *operator->() + { + if constexpr (isRangeModel) + return implementation(); + else + return m_model.get(); + } + + const auto *operator->() const + { + if constexpr (isRangeModel) + return implementation(); + else + return m_model.get(); + } +}; + +} // QRangeModelDetails + +QT_END_NAMESPACE + +#endif // Q_QDOC + +#endif // QRANGEMODELADAPTER_IMPL_H diff --git a/src/corelib/kernel/qassociativeiterable.cpp b/src/corelib/kernel/qassociativeiterable.cpp index 8e3072169dd..8a2fc63c441 100644 --- a/src/corelib/kernel/qassociativeiterable.cpp +++ b/src/corelib/kernel/qassociativeiterable.cpp @@ -7,6 +7,10 @@ QT_BEGIN_NAMESPACE +#if QT_DEPRECATED_SINCE(6, 15) +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED + /*! \class QAssociativeIterator \internal @@ -103,6 +107,7 @@ QVariantConstPointer QAssociativeConstIterator::operator->() const /*! \class QAssociativeIterable + \deprecated [6.15] Use QMetaAssociation::Iterable instead. \since 5.2 \inmodule QtCore \brief The QAssociativeIterable class is an iterable interface for an associative container in a QVariant. @@ -111,8 +116,6 @@ QVariantConstPointer QAssociativeConstIterator::operator->() const a QVariant. An instance of QAssociativeIterable can be extracted from a QVariant if it can be converted to a QVariantHash or QVariantMap or if a custom mutable view has been registered. - \snippet code/src_corelib_kernel_qvariant.cpp 10 - The container itself is not copied before iterating over it. \sa QVariant @@ -270,20 +273,20 @@ void QAssociativeIterable::setValue(const QVariant &key, const QVariant &mapped) /*! \typealias QAssociativeIterable::const_iterator + \deprecated [6.15] Use QMetaAssociation::Iterable::ConstIterator instead. \inmodule QtCore \brief The QAssociativeIterable::const_iterator allows iteration over a container in a QVariant. A QAssociativeIterable::const_iterator can only be created by a QAssociativeIterable instance, and can be used in a way similar to other stl-style iterators. - \snippet code/src_corelib_kernel_qvariant.cpp 10 - \sa QAssociativeIterable */ /*! \typealias QAssociativeIterable::iterator \since 6.0 + \deprecated [6.15] Use QMetaAssociation::Iterable::Iterator instead. \inmodule QtCore \brief The QAssociativeIterable::iterator allows iteration over a container in a QVariant. @@ -293,4 +296,7 @@ void QAssociativeIterable::setValue(const QVariant &key, const QVariant &mapped) \sa QAssociativeIterable */ +QT_WARNING_POP +#endif // QT_DEPRECATED_SINCE(6, 15) + QT_END_NAMESPACE diff --git a/src/corelib/kernel/qassociativeiterable.h b/src/corelib/kernel/qassociativeiterable.h index f3963d350ea..4f9bbf67bfb 100644 --- a/src/corelib/kernel/qassociativeiterable.h +++ b/src/corelib/kernel/qassociativeiterable.h @@ -9,7 +9,21 @@ QT_BEGIN_NAMESPACE -class Q_CORE_EXPORT QAssociativeIterator : public QIterator<QMetaAssociation> +#if QT_DEPRECATED_SINCE(6, 15) +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED + +#if defined(Q_CC_GNU_ONLY) && Q_CC_GNU < 1300 + // GCC < 13 doesn't accept both deprecation and visibility on the same class + #define QT_CORE_DEPRECATED_EXPORT_VERSION_X_6_15(text) Q_CORE_EXPORT +#else + #define QT_CORE_DEPRECATED_EXPORT_VERSION_X_6_15(text) \ + Q_CORE_EXPORT QT_DEPRECATED_VERSION_X_6_15(text) +#endif + +class +QT_CORE_DEPRECATED_EXPORT_VERSION_X_6_15("Use QMetaAssociation::Iterable::Iterator instead.") +QAssociativeIterator : public QIterator<QMetaAssociation> { public: using key_type = QVariant; @@ -28,7 +42,9 @@ public: QVariantPointer<QAssociativeIterator> operator->() const; }; -class Q_CORE_EXPORT QAssociativeConstIterator : public QConstIterator<QMetaAssociation> +class +QT_CORE_DEPRECATED_EXPORT_VERSION_X_6_15("Use QMetaAssociation::Iterable::ConstIterator instead.") +QAssociativeConstIterator : public QConstIterator<QMetaAssociation> { public: using key_type = QVariant; @@ -47,7 +63,9 @@ public: QVariantConstPointer operator->() const; }; -class Q_CORE_EXPORT QAssociativeIterable : public QIterable<QMetaAssociation> +class +QT_CORE_DEPRECATED_EXPORT_VERSION_X_6_15("Use QMetaAssociation::Iterable instead.") +QAssociativeIterable : public QIterable<QMetaAssociation> { public: using iterator = QTaggedIterator<QAssociativeIterator, void>; @@ -86,14 +104,12 @@ public: { } - // ### Qt7: Pass QMetaType as value rather than const ref. QAssociativeIterable(const QMetaAssociation &metaAssociation, const QMetaType &metaType, void *iterable) : QIterable(metaAssociation, metaType.alignOf(), iterable) { } - // ### Qt7: Pass QMetaType as value rather than const ref. QAssociativeIterable(const QMetaAssociation &metaAssociation, const QMetaType &metaType, const void *iterable) : QIterable(metaAssociation, metaType.alignOf(), iterable) @@ -140,10 +156,12 @@ inline QVariantRef<QAssociativeIterator>::operator QVariant() const if (!metaType.isValid()) return m_pointer->key(); + return [&] { QVariant v(metaType); metaAssociation.mappedAtIterator(m_pointer->constIterator(), metaType == QMetaType::fromType<QVariant>() ? &v : v.data()); return v; + }(); } template<> @@ -168,6 +186,11 @@ Q_DECLARE_TYPEINFO(QAssociativeIterable, Q_RELOCATABLE_TYPE); Q_DECLARE_TYPEINFO(QAssociativeIterable::iterator, Q_RELOCATABLE_TYPE); Q_DECLARE_TYPEINFO(QAssociativeIterable::const_iterator, Q_RELOCATABLE_TYPE); +#undef QT_CORE_DEPRECATED_EXPORT_VERSION_X_6_15 + +QT_WARNING_POP +#endif // QT_DEPRECATED_SINCE(6, 15) + QT_END_NAMESPACE #endif // QASSOCIATIVEITERABLE_H diff --git a/src/corelib/kernel/qcore_mac.mm b/src/corelib/kernel/qcore_mac.mm index 687fc7e85fa..f5f1f4a8cb8 100644 --- a/src/corelib/kernel/qcore_mac.mm +++ b/src/corelib/kernel/qcore_mac.mm @@ -22,6 +22,10 @@ #include <spawn.h> #include <qdebug.h> +#include <qpoint.h> +#include <qsize.h> +#include <qrect.h> +#include <qmargins.h> #include "qendian.h" #include "qhash.h" @@ -222,6 +226,34 @@ QDebug operator<<(QDebug dbg, CFStringRef stringRef) return dbg; } +QDebug operator<<(QDebug dbg, CGPoint point) +{ + dbg << QPointF::fromCGPoint(point); + return dbg; +} + +QDebug operator<<(QDebug dbg, CGSize size) +{ + dbg << QSizeF::fromCGSize(size); + return dbg; +} + +QDebug operator<<(QDebug dbg, CGRect rect) +{ + dbg << QRectF::fromCGRect(rect); + return dbg; +} + +#if defined(Q_OS_MACOS) +QDebug operator<<(QDebug dbg, NSEdgeInsets insets) +#else +QDebug operator<<(QDebug dbg, UIEdgeInsets insets) +#endif +{ + dbg << QMargins(insets.left, insets.top, insets.right, insets.bottom); + return dbg; +} + // Prevents breaking the ODR in case we introduce support for more types // later on, and lets the user override our default QDebug operators. #define QT_DECLARE_WEAK_QDEBUG_OPERATOR_FOR_CF_TYPE(CFType) \ diff --git a/src/corelib/kernel/qcore_mac_p.h b/src/corelib/kernel/qcore_mac_p.h index 2c4b4c02c55..1e57ee01e1d 100644 --- a/src/corelib/kernel/qcore_mac_p.h +++ b/src/corelib/kernel/qcore_mac_p.h @@ -78,6 +78,15 @@ kern_return_t IOObjectRelease(io_object_t object); Q_FORWARD_DECLARE_OBJC_CLASS(NSObject); Q_FORWARD_DECLARE_OBJC_CLASS(NSString); +struct CGPoint; +struct CGSize; +struct CGRect; +#if defined(Q_OS_MACOS) +struct NSEdgeInsets; +#else +struct UIEdgeInsets; +#endif + // @compatibility_alias doesn't work with categories or their methods #define QtExtras QT_MANGLE_NAMESPACE(QtExtras) @@ -225,6 +234,14 @@ Q_AUTOTEST_EXPORT void qt_mac_ensureResponsible(); #ifndef QT_NO_DEBUG_STREAM Q_CORE_EXPORT QDebug operator<<(QDebug debug, const QMacAutoReleasePool *pool); Q_CORE_EXPORT QDebug operator<<(QDebug debug, const QCFString &string); +Q_CORE_EXPORT QDebug operator<<(QDebug, CGPoint); +Q_CORE_EXPORT QDebug operator<<(QDebug, CGSize); +Q_CORE_EXPORT QDebug operator<<(QDebug, CGRect); +#if defined(Q_OS_MACOS) +Q_CORE_EXPORT QDebug operator<<(QDebug, NSEdgeInsets); +#else +Q_CORE_EXPORT QDebug operator<<(QDebug, UIEdgeInsets); +#endif #endif Q_CORE_EXPORT bool qt_apple_isApplicationExtension(); diff --git a/src/corelib/kernel/qeventdispatcher_cf.mm b/src/corelib/kernel/qeventdispatcher_cf.mm index 2dddf147f85..d57c057a21a 100644 --- a/src/corelib/kernel/qeventdispatcher_cf.mm +++ b/src/corelib/kernel/qeventdispatcher_cf.mm @@ -207,6 +207,7 @@ QEventLoop *QEventDispatcherCoreFoundation::currentEventLoop() const } /*! + \internal Processes all pending events that match \a flags until there are no more events to process. Returns \c true if pending events were handled; otherwise returns \c false. diff --git a/src/corelib/kernel/qiterable.cpp b/src/corelib/kernel/qiterable.cpp index 976aafd13e5..ca2893e1090 100644 --- a/src/corelib/kernel/qiterable.cpp +++ b/src/corelib/kernel/qiterable.cpp @@ -2,9 +2,12 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include <QtCore/qiterable.h> +#include <QtCore/qloggingcategory.h> QT_BEGIN_NAMESPACE +Q_STATIC_LOGGING_CATEGORY(lcSynthesizedIterableAccess, "qt.iterable.synthesized", QtWarningMsg); + /*! \class QBaseIterator \inmodule QtCore @@ -119,7 +122,7 @@ QT_BEGIN_NAMESPACE A QIterator can only be created by a QIterable instance, and can be used in a way similar to other stl-style iterators. Generally, QIterator should not be used directly, but through its derived classes provided by - QSequentialIterable and QAssociativeIterable. + QMetaSequence::Iterable and QMetaAssociation::Iterable. \sa QIterable */ @@ -155,7 +158,7 @@ QT_BEGIN_NAMESPACE next item in the container and returns an iterator to the new current item. - Calling this function on QSequentialIterable::constEnd() leads to undefined results. + Calling this function on QMetaSequence::Iterable::constEnd() leads to undefined results. \sa operator--() */ @@ -176,7 +179,7 @@ QT_BEGIN_NAMESPACE The prefix \c{--} operator (\c{--it}) makes the preceding item current and returns an iterator to the new current item. - Calling this function on QSequentialIterable::constBegin() leads to undefined results. + Calling this function on QMetaSequence::Iterable::constBegin() leads to undefined results. If the container in the QVariant does not support bi-directional iteration, calling this function leads to undefined results. @@ -389,7 +392,7 @@ QT_BEGIN_NAMESPACE \class QIterable \inmodule QtCore \since 6.0 - \brief QIterable is a template class that is the base class for QSequentialIterable and QAssociativeIterable. + \brief QIterable is a template class that is the base class for QMetaSequence::Iterable and QMetaAssociation::Iterable. */ /*! @@ -454,7 +457,7 @@ QT_BEGIN_NAMESPACE /*! \fn template<class Container> QIterator<Container> QIterable<Container>::mutableEnd() - Returns a QSequentialIterable::iterator for the end of the container. This + Returns a QMetaSequence::Iterable::iterator for the end of the container. This can be used in stl-style iteration. \sa mutableBegin(), constEnd() @@ -464,6 +467,17 @@ QT_BEGIN_NAMESPACE \fn template<class Container> qsizetype QIterable<Container>::size() const Returns the number of values in the container. + + \note If the underlying container does not provide a native way to query + the size, this method will synthesize the access using iterators. + This behavior is deprecated and will be removed in a future version + of Qt. +*/ + +/*! + \fn template<class Container> void QIterable<Container>::clear() + + Clears the container. */ /*! @@ -473,7 +487,7 @@ QT_BEGIN_NAMESPACE \brief QTaggedIterator is a template class that wraps an iterator and exposes standard iterator traits. In order to use an iterator any of the standard algorithms, its iterator - traits need to be known. As QSequentialIterable can work with many different + traits need to be known. As QMetaSequence::Iterable can work with many different kinds of containers, we cannot declare the traits in the iterator classes themselves. A QTaggedIterator gives you a way to explicitly declare a trait for a concrete instance of an iterator or QConstIterator. @@ -512,7 +526,7 @@ QT_BEGIN_NAMESPACE next item in the container and returns an iterator to the new current item. - Calling this function on QSequentialIterable::constEnd() leads to undefined results. + Calling this function on QMetaSequence::Iterable::constEnd() leads to undefined results. \sa operator--() */ @@ -533,7 +547,7 @@ QT_BEGIN_NAMESPACE The prefix \c{--} operator (\c{--it}) makes the preceding item current and returns an iterator to the new current item. - Calling this function on QSequentialIterable::constBegin() leads to undefined results. + Calling this function on QMetaSequence::Iterable::constBegin() leads to undefined results. If the container in the QVariant does not support bi-directional iteration, calling this function leads to undefined results. @@ -609,4 +623,12 @@ QT_BEGIN_NAMESPACE \sa operator+(), operator-=(), QIterable::canReverseIterate() */ +/*! + \internal + */ +void QtPrivate::warnSynthesizedAccess(const char *text) +{ + qCWarning(lcSynthesizedIterableAccess, "%s", text); +} + QT_END_NAMESPACE diff --git a/src/corelib/kernel/qiterable.h b/src/corelib/kernel/qiterable.h index 5e25dd1c0a5..494cec73a3f 100644 --- a/src/corelib/kernel/qiterable.h +++ b/src/corelib/kernel/qiterable.h @@ -64,6 +64,8 @@ namespace QtPrivate { return m_pointer.tag() == Mutable ? reinterpret_cast<Type *>(m_pointer.data()) : nullptr; } }; + + Q_CORE_EXPORT void warnSynthesizedAccess(const char *text); } template<class Iterator, typename IteratorCategory> @@ -499,6 +501,14 @@ public: const void *container = constIterable(); if (m_metaContainer.hasSize()) return m_metaContainer.size(container); + +#if QT_VERSION >= QT_VERSION_CHECK(7, 0, 0) + // We shouldn't second-guess the underlying container, so we're not synthesizing a size. + return -1; +#else + QtPrivate::warnSynthesizedAccess( + "size() called on an iterable without native size accessor. This is slow"); + if (!m_metaContainer.hasConstIterator()) return -1; @@ -508,6 +518,12 @@ public: m_metaContainer.destroyConstIterator(begin); m_metaContainer.destroyConstIterator(end); return size; +#endif + } + + void clear() + { + m_metaContainer.clear(mutableIterable()); } Container metaContainer() const diff --git a/src/corelib/kernel/qjniarray.h b/src/corelib/kernel/qjniarray.h index 13349688d20..97d0cd37682 100644 --- a/src/corelib/kernel/qjniarray.h +++ b/src/corelib/kernel/qjniarray.h @@ -872,7 +872,7 @@ auto QJniArrayBase::makeArray(List &&list, NewFn &&newArray, SetFn &&setRegion) const size_type length = size_type(std::size(list)); JNIEnv *env = QJniEnvironment::getJniEnv(); auto localArray = (env->*newArray)(length); - if (QJniEnvironment::checkAndClearExceptions(env)) { + if (env->ExceptionCheck()) { if (localArray) env->DeleteLocalRef(localArray); return QJniArray<ElementType>(); @@ -916,7 +916,7 @@ auto QJniArrayBase::makeObjectArray(List &&list) elementClass = env->GetObjectClass(*std::begin(list)); } auto localArray = env->NewObjectArray(length, elementClass, nullptr); - if (QJniEnvironment::checkAndClearExceptions(env)) { + if (env->ExceptionCheck()) { if (localArray) env->DeleteLocalRef(localArray); return ResultType(); diff --git a/src/corelib/kernel/qjnienvironment.cpp b/src/corelib/kernel/qjnienvironment.cpp index b4f8497ddda..1ee658fd18d 100644 --- a/src/corelib/kernel/qjnienvironment.cpp +++ b/src/corelib/kernel/qjnienvironment.cpp @@ -559,4 +559,12 @@ bool QJniEnvironment::checkAndClearExceptions(JNIEnv *env, QJniEnvironment::Outp return false; } +/*! + Returns the stack trace that resulted in \a exception being thrown. +*/ +QStringList QJniEnvironment::stackTrace(jthrowable exception) +{ + return exceptionMessage(getJniEnv(), exception).split(u'\n'); +} + QT_END_NAMESPACE diff --git a/src/corelib/kernel/qjnienvironment.h b/src/corelib/kernel/qjnienvironment.h index b473f75bed1..a5f3700b1f0 100644 --- a/src/corelib/kernel/qjnienvironment.h +++ b/src/corelib/kernel/qjnienvironment.h @@ -89,6 +89,7 @@ public: static bool checkAndClearExceptions(JNIEnv *env, OutputMode outputMode = OutputMode::Verbose); static JNIEnv *getJniEnv(); + static QStringList stackTrace(jthrowable exception); private: Q_DISABLE_COPY_MOVE(QJniEnvironment) diff --git a/src/corelib/kernel/qjniobject.cpp b/src/corelib/kernel/qjniobject.cpp index 8f3da9a8595..21bfe5af448 100644 --- a/src/corelib/kernel/qjniobject.cpp +++ b/src/corelib/kernel/qjniobject.cpp @@ -27,7 +27,8 @@ using namespace Qt::StringLiterals; garbage-collected and providing access to most \c JNIEnv method calls (member, static) and fields (setter, getter). It eliminates much boiler-plate that would normally be needed, with direct JNI access, for - every operation, including exception-handling. + every operation. Exceptions thrown by called Java methods are cleared by + default, but can since Qt 6.11 also be handled by the caller. \note This API has been designed and tested for use with Android. It has not been tested for other platforms. @@ -129,12 +130,46 @@ using namespace Qt::StringLiterals; Note that while the first template parameter specifies the return type of the Java function, the method will still return a QJniObject. - \section1 Handling Java Exception + \section1 Handling Java Exceptions After calling Java functions that might throw exceptions, it is important to check for, handle and clear out any exception before continuing. All - QJniObject functions handle exceptions internally by reporting and clearing them, - saving client code the need to handle exceptions. + QJniObject functions can handle exceptions internally by reporting and + clearing them. This includes JNI exceptions, for instance when trying to + call a method that doesn't exist, or with bad parameters; and exceptions + are thrown by the method as a way of reporting errors or returning failure + information. + + From Qt 6.11 on, client code can opt in to handle exceptions explicitly in + each call. To do so, use \c{std::expected} from C++ 23 as the return type, + with the value type as the expected, and \c{jthrowable} as the error type. + For instance, trying to read a setting value via the + \c{android.provider.Settings.Secure} type might throw an exception if the + setting does not exist. + + \code + Q_DECLARE_JNI_CLASS(SettingsSecure, "android/provider/Settings$Secure") + using namespace QtJniTypes; + + QString enabledInputMethods() + { + ContentResolver resolver; + SettingsSecure settings; + + auto defaultInputMethods = settings.callMethod<std::expected<QString, jthrowable>>( + "getString", resolver, u"enabled_input_methods"_s + ); + if (defaultInputMethods) + return defaultInputMethods.value(); + QStringList stackTrace = QJniEnvironment::stackTrace(defaultInputMethods.error()); + } + \endcode + + You can use any other type that behaves like \c{std::expected}, so handling + exceptions explicitly is possible without using C++23. The only + requirements are that the type declares three nested types \c{value_type}, + \c{error_type}, and \c{unexpected_type}, can be constructed from the value + type, and from its \c{unexpected_type} holding a \c{jthrowable}. \note The user must handle exceptions manually when doing JNI calls using \c JNIEnv directly. It is unsafe to make other JNI calls when exceptions are pending. For more information, see @@ -348,7 +383,10 @@ static inline QByteArray cacheKey(Args &&...args) return (QByteArrayView(":") + ... + QByteArrayView(args)); } -typedef QHash<QByteArray, jclass> JClassHash; +struct JClassHash : QHash<QByteArray, jclass> +{ + jmethodID loadClassMethod = 0; +}; Q_GLOBAL_STATIC(JClassHash, cachedClasses) Q_GLOBAL_STATIC(QReadWriteLock, cachedClassesLock) @@ -391,29 +429,22 @@ bool QJniObjectPrivate::isJString(JNIEnv *env) const */ static QJniObject getCleanJniObject(jobject object, JNIEnv *env) { - if (QJniEnvironment::checkAndClearExceptions(env) || !object) { - if (object) - env->DeleteLocalRef(object); + if (!object || env->ExceptionCheck()) return QJniObject(); - } - QJniObject res(object); - env->DeleteLocalRef(object); - return res; + return QJniObject::fromLocalRef(object); } -/*! - \internal - \a className must be slash-encoded -*/ -jclass QtAndroidPrivate::findClass(const char *className, JNIEnv *env) +namespace { + +jclass loadClassHelper(const QByteArray &className, JNIEnv *env) { Q_ASSERT(env); QByteArray classNameArray(className); #ifdef QT_DEBUG if (classNameArray.contains('.')) { qWarning("QtAndroidPrivate::findClass: className '%s' should use slash separators!", - className); + className.constData()); } #endif classNameArray.replace('.', '/'); @@ -442,21 +473,40 @@ jclass QtAndroidPrivate::findClass(const char *className, JNIEnv *env) if (!clazz) { // Wrong class loader, try our own - QJniObject classLoader(QtAndroidPrivate::classLoader()); - if (!classLoader.isValid()) + jobject classLoader = QtAndroidPrivate::classLoader(); + if (!classLoader) return nullptr; + if (!cachedClasses->loadClassMethod) { + jclass classLoaderClass = env->GetObjectClass(classLoader); + if (!classLoaderClass) + return nullptr; + cachedClasses->loadClassMethod = + env->GetMethodID(classLoaderClass, + "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;"); + env->DeleteLocalRef(classLoaderClass); + if (!cachedClasses->loadClassMethod) { + qCritical("Couldn't find the 'loadClass' method in the Qt class loader"); + return nullptr; + } + } + // ClassLoader::loadClass on the other hand wants the binary name of the class, // e.g. dot-separated. In testing it works also with /, but better to stick to // the specification. const QString binaryClassName = QString::fromLatin1(className).replace(u'/', u'.'); - jstring classNameObject = env->NewString(reinterpret_cast<const jchar*>(binaryClassName.constData()), - binaryClassName.length()); - QJniObject classObject = classLoader.callMethod<jclass>("loadClass", classNameObject); + jstring classNameObject = env->NewString(binaryClassName.utf16(), binaryClassName.length()); + jobject classObject = env->CallObjectMethod(classLoader, + cachedClasses->loadClassMethod, + classNameObject); env->DeleteLocalRef(classNameObject); - if (!QJniEnvironment::checkAndClearExceptions(env) && classObject.isValid()) - clazz = static_cast<jclass>(env->NewGlobalRef(classObject.object())); + if (classObject && !env->ExceptionCheck()) { + clazz = static_cast<jclass>(env->NewGlobalRef(classObject)); + env->DeleteLocalRef(classObject); + } + // Clearing the exception is the caller's responsibility (see + // QtAndroidPrivate::findClass()) and QJniObject::loadClass{KeepExceptions} } if (clazz) @@ -465,11 +515,32 @@ jclass QtAndroidPrivate::findClass(const char *className, JNIEnv *env) return clazz; } +} // unnamed namespace + +/*! + \internal + \a className must be slash-encoded +*/ +jclass QtAndroidPrivate::findClass(const char *className, JNIEnv *env) +{ + jclass clazz = loadClassHelper(className, env); + if (!clazz) + QJniEnvironment::checkAndClearExceptions(env); + return clazz; +} + jclass QJniObject::loadClass(const QByteArray &className, JNIEnv *env) { return QtAndroidPrivate::findClass(className, env); } +jclass QJniObject::loadClassKeepExceptions(const QByteArray &className, JNIEnv *env) +{ + return loadClassHelper(className, env); +} + + + typedef QHash<QByteArray, jmethodID> JMethodIDHash; Q_GLOBAL_STATIC(JMethodIDHash, cachedMethodID) Q_GLOBAL_STATIC(QReadWriteLock, cachedMethodIDLock) @@ -483,9 +554,6 @@ jmethodID QJniObject::getMethodID(JNIEnv *env, jmethodID id = isStatic ? env->GetStaticMethodID(clazz, name, signature) : env->GetMethodID(clazz, name, signature); - if (QJniEnvironment::checkAndClearExceptions(env)) - return nullptr; - return id; } @@ -525,7 +593,8 @@ jmethodID QJniObject::getCachedMethodID(JNIEnv *env, jmethodID id = getMethodID(env, clazz, name, signature, isStatic); - cachedMethodID->insert(key, id); + if (id) + cachedMethodID->insert(key, id); return id; } } @@ -549,9 +618,6 @@ jfieldID QJniObject::getFieldID(JNIEnv *env, jfieldID id = isStatic ? env->GetStaticFieldID(clazz, name, signature) : env->GetFieldID(clazz, name, signature); - if (QJniEnvironment::checkAndClearExceptions(env)) - return nullptr; - return id; } @@ -583,7 +649,8 @@ jfieldID QJniObject::getCachedFieldID(JNIEnv *env, jfieldID id = getFieldID(env, clazz, name, signature, isStatic); - cachedFieldID->insert(key, id); + if (id) + cachedFieldID->insert(key, id); return id; } } @@ -889,7 +956,10 @@ QByteArray QJniObject::className() const jint size = myJavaString.callMethod<jint>("length"); \endcode - The method signature is deduced at compile time from \c Ret and the types of \a args. + The method signature is deduced at compile time from \c Ret and the types + of \a args. \c Ret can be a \c{std::expected}-compatible type that returns + a value, or \l{Handling Java Exceptions}{any Java exception thrown} by the + called method. */ /*! @@ -920,7 +990,10 @@ QByteArray QJniObject::className() const jint value = QJniObject::callStaticMethod<jint>("MyClass", "staticMethod"); \endcode - The method signature is deduced at compile time from \c Ret and the types of \a args. + The method signature is deduced at compile time from \c Ret and the types + of \a args. \c Ret can be a \c{std::expected}-compatible type that returns + a value, or \l{Handling Java Exceptions}{any Java exception thrown} by the + called method. */ /*! @@ -977,7 +1050,10 @@ QByteArray QJniObject::className() const jdouble randNr = QJniObject::callStaticMethod<jdouble>(javaMathClass, "random"); \endcode - The method signature is deduced at compile time from \c Ret and the types of \a args. + The method signature is deduced at compile time from \c Ret and the types + of \a args. \c Ret can be a \c{std::expected}-compatible type that returns + a value, or \l{Handling Java Exceptions}{any Java exception thrown} by the + called method. */ /*! @@ -988,8 +1064,12 @@ QByteArray QJniObject::className() const \c Ret (unless \c Ret is \c void). If \c Ret is a jobject type, then the returned value will be a QJniObject. - The method signature is deduced at compile time from \c Ret and the types of \a args. - \c Klass needs to be a C++ type with a registered type mapping to a Java type. + The method signature is deduced at compile time from \c Ret and the types + of \a args. \c Klass needs to be a C++ type with a registered type mapping + to a Java type. \c Ret can be a \c{std::expected}-compatible type that + returns a value, or \l{Handling Java Exceptions}{any Java exception thrown} + by the called method. + */ /*! @@ -1008,15 +1088,15 @@ QJniObject QJniObject::callObjectMethod(const char *methodName, const char *sign { JNIEnv *env = jniEnv(); jmethodID id = getCachedMethodID(env, methodName, signature); - if (id) { - va_list args; - va_start(args, signature); - QJniObject res = getCleanJniObject(env->CallObjectMethodV(d->m_jobject, id, args), env); - va_end(args); - return res; - } + va_list args; + va_start(args, signature); + // can't go back from variadic arguments to variadic templates + jobject object = id ? jniEnv()->CallObjectMethodV(javaObject(), id, args) : nullptr; + QJniObject res = getCleanJniObject(object, env); + va_end(args); - return QJniObject(); + QJniEnvironment::checkAndClearExceptions(env); + return res; } /*! @@ -1118,7 +1198,10 @@ QJniObject QJniObject::callStaticObjectMethod(jclass clazz, jmethodID methodId, QJniObject myJavaString2 = myJavaString1.callObjectMethod<jstring>("toString"); \endcode - The method signature is deduced at compile time from \c Ret and the types of \a args. + The method signature is deduced at compile time from \c Ret and the types + of \a args. \c Ret can be a \c{std::expected}-compatible type that returns + a value, or \l{Handling Java Exceptions}{any Java exception thrown} by the + called method. */ /*! @@ -1132,7 +1215,10 @@ QJniObject QJniObject::callStaticObjectMethod(jclass clazz, jmethodID methodId, QJniObject string = QJniObject::callStaticObjectMethod<jstring>("CustomClass", "getClassName"); \endcode - The method signature is deduced at compile time from \c Ret and the types of \a args. + The method signature is deduced at compile time from \c Ret and the types + of \a args. \c Ret can be a \c{std::expected}-compatible type that returns + a value, or \l{Handling Java Exceptions}{any Java exception thrown} by the + called method. */ /*! @@ -1150,7 +1236,7 @@ QJniObject QJniObject::callStaticObjectMethod(jclass clazz, jmethodID methodId, */ /*! - \fn template <typename T> void QJniObject::setStaticField(const char *className, const char *fieldName, const char *signature, T value); + \fn template <typename Ret, typename Type> auto QJniObject::setStaticField(const char *className, const char *fieldName, const char *signature, Type value); Sets the static field \a fieldName on the class \a className to \a value using the setter with \a signature. @@ -1158,7 +1244,7 @@ QJniObject QJniObject::callStaticObjectMethod(jclass clazz, jmethodID methodId, */ /*! - \fn template <typename T> void QJniObject::setStaticField(jclass clazz, const char *fieldName, const char *signature, T value); + \fn template <typename Ret, typename Type> auto QJniObject::setStaticField(jclass clazz, const char *fieldName, const char *signature, Type value); Sets the static field \a fieldName on the class \a clazz to \a value using the setter with \a signature. @@ -1196,19 +1282,19 @@ QJniObject QJniObject::callStaticObjectMethod(jclass clazz, jmethodID methodId, */ /*! - \fn template <typename T> void QJniObject::setStaticField(const char *className, const char *fieldName, T value) + \fn template <typename Ret, typename Type> auto QJniObject::setStaticField(const char *className, const char *fieldName, Type value) Sets the static field \a fieldName of the class \a className to \a value. */ /*! - \fn template <typename T> void QJniObject::setStaticField(jclass clazz, const char *fieldName, T value) + \fn template <typename Ret, typename Type> auto QJniObject::setStaticField(jclass clazz, const char *fieldName, Type value) Sets the static field \a fieldName of the class \a clazz to \a value. */ /*! - \fn template <typename Klass, typename T> auto QJniObject::setStaticField(const char *fieldName, T value) + \fn template <typename Klass, typename Ret, typename Type> auto QJniObject::setStaticField(const char *fieldName, Type value) Sets the static field \a fieldName of the class \c Klass to \a value. @@ -1263,11 +1349,16 @@ QJniObject QJniObject::getStaticObjectField(jclass clazz, const char *fieldName, { JNIEnv *env = QJniEnvironment::getJniEnv(); jfieldID id = getFieldID(env, clazz, fieldName, signature, true); - return getCleanJniObject(env->GetStaticObjectField(clazz, id), env); + + const auto clearExceptions = qScopeGuard([env]{ + QJniEnvironment::checkAndClearExceptions(env); + }); + + return getCleanJniObject(getStaticObjectFieldImpl(env, clazz, id), env); } /*! - \fn template <typename T> void QJniObject::setField(const char *fieldName, const char *signature, T value) + \fn template <typename Ret, typename Type> void QJniObject::setField(const char *fieldName, const char *signature, Type value) Sets the value of \a fieldName with \a signature to \a value. @@ -1304,14 +1395,16 @@ QJniObject QJniObject::getObjectField(const char *fieldName, const char *signatu { JNIEnv *env = jniEnv(); jfieldID id = getCachedFieldID(env, fieldName, signature); - if (!id) - return QJniObject(); - return getCleanJniObject(env->GetObjectField(d->m_jobject, id), env); + const auto clearExceptions = qScopeGuard([env]{ + QJniEnvironment::checkAndClearExceptions(env); + }); + + return getCleanJniObject(getObjectFieldImpl(env, id), env); } /*! - \fn template <typename T> void QJniObject::setField(const char *fieldName, T value) + \fn template <typename Ret, typename Type> void QJniObject::setField(const char *fieldName, Type value) Sets the value of \a fieldName to \a value. @@ -1488,4 +1581,179 @@ jobject QJniObject::javaObject() const return d->m_jobject; } +/*! + \class QtJniTypes::JObjectBase + \brief The JObjectBase in the QtJniTypes namespace is the base of all declared Java types. + \inmodule QtCore + \internal +*/ + +/*! + \class QtJniTypes::JObject + \inmodule QtCore + \brief The JObject template in the QtJniTypes namespace is the base of declared Java types. + \since Qt 6.8 + + This template gets specialized when using the Q_DECLARE_JNI_CLASS macro. The + specialization produces a unique type in the QtJniTypes namespace. This + allows the type system to deduce the correct signature in JNI calls when an + instance of the specialized type is passed as a parameter. + + Instances can be implicitly converted to and from QJniObject and jobject, + and provide the same template API as QJniObject to call methods and access + properties. Since instances of JObject know about the Java type they hold, + APIs to access static methods or fields do not require the class name as an + explicit parameter. + + \sa Q_DECLARE_JNI_CLASS +*/ + +/*! + \fn template <typename Type> QtJniTypes::JObject<Type>::JObject() + + Default-constructs the JObject instance. This also default-constructs an + instance of the represented Java type. +*/ + +/*! + \fn template <typename Type> QtJniTypes::JObject<Type>::JObject(const QJniObject &other) + + Constructs a JObject instance that holds a reference to the same jobject as \a other. +*/ + +/*! + \fn template <typename Type> QtJniTypes::JObject<Type>::JObject(jobject other) + + Constructs a JObject instance that holds a reference to \a other. +*/ + +/*! + \fn template <typename Type> QtJniTypes::JObject<Type>::JObject(QJniObject &&other) + + Move-constructs a JObject instance from \a other. +*/ + +/*! + \fn template <typename Type> bool QtJniTypes::JObject<Type>::isValid() const + + Returns whether the JObject instance holds a valid reference to a jobject. + + \sa QJniObject::isValid() +*/ + +/*! + \fn template <typename Type> jclass QtJniTypes::JObject<Type>::objectClass() const + + Returns the Java class that this JObject is an instance of as a jclass. + + \sa className(), QJniObject::objectClass() +*/ + +/*! + \fn template <typename Type> QString QtJniTypes::JObject<Type>::toString() const + + Returns a QString with a string representation of the Java object. + + \sa QJniObject::toString() +*/ + +/*! + \fn template <typename Type> QByteArray QtJniTypes::JObject<Type>::className() const + + Returns the name of the Java class that this object is an instance of. + + \sa objectClass(), QJniObject::className() +*/ + +/*! + \fn template <typename Type> bool QtJniTypes::JObject<Type>::isClassAvailable() + + Returns whether the class that this JObject specialization represents is + available. + + \sa QJniObject::isClassAvailable() +*/ + +/*! + \fn template <typename Type> JObject QtJniTypes::JObject<Type>::fromJObject(jobject object) + + Constructs a JObject instance from \a object and returns that instance. +*/ + +/*! + \fn template <typename Type> template <typename ...Args> JObject QtJniTypes::JObject<Type>::construct(Args &&...args) + + Constructs a Java object from \a args and returns a JObject instance that + holds a reference to that Java object. +*/ + +/*! + \fn template <typename Type> JObject QtJniTypes::JObject<Type>::fromLocalRef(jobject ref) + + Constructs a JObject that holds a local reference to \a ref, and returns + that object. +*/ + +/*! + \fn template <typename Type> template <typename Ret, typename ...Args> auto QtJniTypes::JObject<Type>::callStaticMethod(const char *methodName, Args &&...args) + + Calls the static method \a methodName with arguments \a args, and returns + the result of type \c Ret (unless \c Ret is \c void). If \c Ret is a + jobject type, then the returned value will be a QJniObject. + + \sa QJniObject::callStaticMethod() +*/ + +/*! + \fn template <typename Type> bool QtJniTypes::JObject<Type>::registerNativeMethods(std::initializer_list<JNINativeMethod> methods) + + Registers the Java methods in \a methods with the Java class represented by + the JObject specialization, and returns whether the registration was successful. + + \sa QJniEnvironment::registerNativeMethods() +*/ + +/*! + \fn template <typename Type> template <typename T> auto QtJniTypes::JObject<Type>::getStaticField(const char *field) + + Returns the value of the static field \a field. + + \sa QJniObject::getStaticField() +*/ + +/*! + \fn template <typename Type> template <typename Ret, typename T> auto QtJniTypes::JObject<Type>::setStaticField(const char *field, T &&value) + + Sets the static field \a field to \a value. + + \sa QJniObject::setStaticField() +*/ + +/*! + \fn template <typename Type> template <typename Ret, typename ...Args> auto QtJniTypes::JObject<Type>::callMethod(const char *method, Args &&...args) const + + Calls the instance method \a method with arguments \a args, and returns + the result of type \c Ret (unless \c Ret is \c void). If \c Ret is a + jobject type, then the returned value will be a QJniObject. + + \sa QJniObject::callMethod() +*/ + +/*! + \fn template <typename Type> template <typename T> auto QtJniTypes::JObject<Type>::getField(const char *field) const + + Returns the value of the instance field \a field. + + \sa QJniObject::getField() +*/ + +/*! + \fn template <typename Type> template <typename Ret, typename T> auto QtJniTypes::JObject<Type>::setField(const char *field, T &&value) + + Sets the value of the instance field \a field to \a value. + + \sa QJniObject::setField() +*/ + + QT_END_NAMESPACE diff --git a/src/corelib/kernel/qjniobject.h b/src/corelib/kernel/qjniobject.h index 236a49544be..c38bf60e051 100644 --- a/src/corelib/kernel/qjniobject.h +++ b/src/corelib/kernel/qjniobject.h @@ -27,10 +27,29 @@ struct StoresGlobalRefTest<T, std::void_t<decltype(std::declval<T>().object())>> : std::is_same<decltype(std::declval<T>().object()), jobject> {}; -template <typename ...Args> +// detect if a type is std::expected-like +template <typename R, typename = void> +struct CallerHandlesException : std::false_type { + using value_type = R; +}; +template <typename R> +struct CallerHandlesException<R, std::void_t<typename R::unexpected_type, + typename R::value_type, + typename R::error_type>> : std::true_type +{ + using value_type = typename R::value_type; +}; + +template <typename ReturnType> +static constexpr bool callerHandlesException = CallerHandlesException<ReturnType>::value; + +template <typename Ret, typename ...Args> struct LocalFrame { + using ReturnType = Ret; + mutable JNIEnv *env; bool hasFrame = false; + explicit LocalFrame(JNIEnv *env = nullptr) noexcept : env(env) { @@ -52,9 +71,12 @@ struct LocalFrame { env = QJniEnvironment::getJniEnv(); return env; } - bool checkAndClearExceptions() + bool checkAndClearExceptions() const { - return env ? QJniEnvironment::checkAndClearExceptions(env) : false; + if constexpr (callerHandlesException<ReturnType>) + return false; + else + return QJniEnvironment::checkAndClearExceptions(jniEnv()); } template <typename T> auto convertToJni(T &&value) @@ -79,13 +101,46 @@ struct LocalFrame { using Type = q20::remove_cvref_t<T>; return QtJniTypes::Traits<Type>::convertFromJni(std::move(object)); } + + template <typename T> + auto convertFromJni(jobject object); + + auto makeResult() + { + if constexpr (callerHandlesException<ReturnType>) { + JNIEnv *env = jniEnv(); + if (env->ExceptionCheck()) { + jthrowable exception = env->ExceptionOccurred(); + env->ExceptionClear(); + return ReturnType(typename ReturnType::unexpected_type(exception)); + } + return ReturnType(); + } else { + checkAndClearExceptions(); + } + } + + template <typename Value> + auto makeResult(Value &&value) + { + if constexpr (callerHandlesException<ReturnType>) { + auto maybeValue = makeResult(); + if (maybeValue) + return ReturnType(std::forward<Value>(value)); + return std::move(maybeValue); + } else { + checkAndClearExceptions(); + return std::forward<Value>(value); + } + } }; } } class Q_CORE_EXPORT QJniObject { - template <typename ...Args> using LocalFrame = QtJniTypes::Detail::LocalFrame<Args...>; + template <typename Ret, typename ...Args> using LocalFrame + = QtJniTypes::Detail::LocalFrame<Ret, Args...>; public: QJniObject(); @@ -97,12 +152,12 @@ public: #endif > explicit QJniObject(const char *className, Args &&...args) - : QJniObject(LocalFrame<Args...>{}, className, std::forward<Args>(args)...) + : QJniObject(LocalFrame<QJniObject, Args...>{}, className, std::forward<Args>(args)...) { } private: template<typename ...Args> - explicit QJniObject(LocalFrame<Args...> localFrame, const char *className, Args &&...args) + explicit QJniObject(LocalFrame<QJniObject, Args...> localFrame, const char *className, Args &&...args) : QJniObject(className, QtJniTypes::constructorSignature<Args...>().data(), localFrame.convertToJni(std::forward<Args>(args))...) { @@ -130,13 +185,23 @@ public: void swap(QJniObject &other) noexcept { d.swap(other.d); } - template<typename Class, typename ...Args> - static inline QJniObject construct(Args &&...args) + template<typename Class, typename ...Args +#ifndef Q_QDOC + , QtJniTypes::IfValidSignatureTypes<Class, Args...> = true +#endif + > + static inline auto construct(Args &&...args) { - LocalFrame<Args...> frame; - return QJniObject(QtJniTypes::Traits<Class>::className().data(), - QtJniTypes::constructorSignature<Args...>().data(), - frame.convertToJni(std::forward<Args>(args))...); + LocalFrame<Class, Args...> frame; + jclass clazz = QJniObject::loadClassKeepExceptions(QtJniTypes::Traits<Class>::className().data(), + frame.jniEnv()); + auto res = clazz + ? QJniObject(clazz, QtJniTypes::constructorSignature<Args...>().data(), + frame.convertToJni(std::forward<Args>(args))...) + : QtJniTypes::Detail::callerHandlesException<Class> + ? QJniObject(Qt::Initialization::Uninitialized) + : QJniObject(); + return frame.makeResult(std::move(res)); } jobject object() const; @@ -149,47 +214,49 @@ public: jclass objectClass() const; QByteArray className() const; - template <typename Ret = void, typename ...Args + template <typename ReturnType = void, typename ...Args #ifndef Q_QDOC - , QtJniTypes::IfValidFieldType<Ret> = true + , QtJniTypes::IfValidFieldType<ReturnType> = true #endif > auto callMethod(const char *methodName, const char *signature, Args &&...args) const { - LocalFrame<Args...> frame(jniEnv()); + using Ret = typename QtJniTypes::Detail::CallerHandlesException<ReturnType>::value_type; + LocalFrame<ReturnType, Args...> frame(jniEnv()); + jmethodID id = getCachedMethodID(frame.jniEnv(), methodName, signature); + if constexpr (QtJniTypes::isObjectType<Ret>()) { - return frame.template convertFromJni<Ret>(callObjectMethod(methodName, signature, - frame.convertToJni(std::forward<Args>(args))...)); + return frame.makeResult(frame.template convertFromJni<Ret>(callObjectMethodImpl( + id, frame.convertToJni(std::forward<Args>(args))...)) + ); } else { - jmethodID id = getCachedMethodID(frame.jniEnv(), methodName, signature); if (id) { if constexpr (std::is_same_v<Ret, void>) { callVoidMethodV(frame.jniEnv(), id, frame.convertToJni(std::forward<Args>(args))...); - frame.checkAndClearExceptions(); } else { Ret res{}; callMethodForType<Ret>(frame.jniEnv(), res, object(), id, frame.convertToJni(std::forward<Args>(args))...); - if (frame.checkAndClearExceptions()) - res = {}; - return res; + return frame.makeResult(res); } } if constexpr (!std::is_same_v<Ret, void>) - return Ret{}; + return frame.makeResult(Ret{}); + else + return frame.makeResult(); } } - template <typename Ret = void, typename ...Args + template <typename ReturnType = void, typename ...Args #ifndef Q_QDOC - , QtJniTypes::IfValidSignatureTypes<Ret, Args...> = true + , QtJniTypes::IfValidSignatureTypes<ReturnType, Args...> = true #endif > auto callMethod(const char *methodName, Args &&...args) const { - constexpr auto signature = QtJniTypes::methodSignature<Ret, Args...>(); - return callMethod<Ret>(methodName, signature.data(), std::forward<Args>(args)...); + constexpr auto signature = QtJniTypes::methodSignature<ReturnType, Args...>(); + return callMethod<ReturnType>(methodName, signature.data(), std::forward<Args>(args)...); } template <typename Ret, typename ...Args @@ -201,9 +268,11 @@ public: { QtJniTypes::assertObjectType<Ret>(); constexpr auto signature = QtJniTypes::methodSignature<Ret, Args...>(); - LocalFrame<Args...> frame(jniEnv()); - return frame.template convertFromJni<Ret>(callObjectMethod(methodName, signature, + LocalFrame<Ret, Args...> frame(jniEnv()); + auto object = frame.template convertFromJni<Ret>(callObjectMethod(methodName, signature, frame.convertToJni(std::forward<Args>(args))...)); + frame.checkAndClearExceptions(); + return object; } QJniObject callObjectMethod(const char *methodName, const char *signature, ...) const; @@ -211,90 +280,93 @@ public: template <typename Ret = void, typename ...Args> static auto callStaticMethod(const char *className, const char *methodName, const char *signature, Args &&...args) { - JNIEnv *env = QJniEnvironment::getJniEnv(); - jclass clazz = QJniObject::loadClass(className, env); + LocalFrame<Ret, Args...> frame; + jclass clazz = QJniObject::loadClass(className, frame.jniEnv()); return callStaticMethod<Ret>(clazz, methodName, signature, std::forward<Args>(args)...); } template <typename Ret = void, typename ...Args> static auto callStaticMethod(jclass clazz, const char *methodName, const char *signature, Args &&...args) { - JNIEnv *env = QJniEnvironment::getJniEnv(); - jmethodID id = clazz ? getMethodID(env, clazz, methodName, signature, true) + LocalFrame<Ret, Args...> frame; + jmethodID id = clazz ? getMethodID(frame.jniEnv(), clazz, methodName, signature, true) : 0; return callStaticMethod<Ret>(clazz, id, std::forward<Args>(args)...); } - template <typename Ret = void, typename ...Args + template <typename ReturnType = void, typename ...Args #ifndef Q_QDOC - , QtJniTypes::IfValidFieldType<Ret> = true + , QtJniTypes::IfValidFieldType<ReturnType> = true #endif > static auto callStaticMethod(jclass clazz, jmethodID methodId, Args &&...args) { - LocalFrame<Args...> frame; + using Ret = typename QtJniTypes::Detail::CallerHandlesException<ReturnType>::value_type; + LocalFrame<ReturnType, Args...> frame; if constexpr (QtJniTypes::isObjectType<Ret>()) { - return frame.template convertFromJni<Ret>(callStaticObjectMethod(clazz, methodId, - frame.convertToJni(std::forward<Args>(args))...)); + return frame.makeResult(frame.template convertFromJni<Ret>(callStaticObjectMethod( + clazz, methodId, + frame.convertToJni(std::forward<Args>(args))...)) + ); } else { if (clazz && methodId) { if constexpr (std::is_same_v<Ret, void>) { callStaticMethodForVoid(frame.jniEnv(), clazz, methodId, frame.convertToJni(std::forward<Args>(args))...); - frame.checkAndClearExceptions(); } else { Ret res{}; callStaticMethodForType<Ret>(frame.jniEnv(), res, clazz, methodId, frame.convertToJni(std::forward<Args>(args))...); - if (frame.checkAndClearExceptions()) - res = {}; - return res; + return frame.makeResult(res); } } if constexpr (!std::is_same_v<Ret, void>) - return Ret{}; + return frame.makeResult(Ret{}); + else + return frame.makeResult(); } } - template <typename Ret = void, typename ...Args + template <typename ReturnType = void, typename ...Args #ifndef Q_QDOC - , QtJniTypes::IfValidSignatureTypes<Ret, Args...> = true + , QtJniTypes::IfValidSignatureTypes<ReturnType, Args...> = true #endif > static auto callStaticMethod(const char *className, const char *methodName, Args &&...args) { - JNIEnv *env = QJniEnvironment::getJniEnv(); - jclass clazz = QJniObject::loadClass(className, env); - const jmethodID id = clazz ? getMethodID(env, clazz, methodName, + using Ret = typename QtJniTypes::Detail::CallerHandlesException<ReturnType>::value_type; + LocalFrame<Ret, Args...> frame; + jclass clazz = QJniObject::loadClass(className, frame.jniEnv()); + const jmethodID id = clazz ? getMethodID(frame.jniEnv(), clazz, methodName, QtJniTypes::methodSignature<Ret, Args...>().data(), true) : 0; - return callStaticMethod<Ret>(clazz, id, std::forward<Args>(args)...); + return callStaticMethod<ReturnType>(clazz, id, std::forward<Args>(args)...); } - template <typename Ret = void, typename ...Args + template <typename ReturnType = void, typename ...Args #ifndef Q_QDOC - , QtJniTypes::IfValidSignatureTypes<Ret, Args...> = true + , QtJniTypes::IfValidSignatureTypes<ReturnType, Args...> = true #endif > static auto callStaticMethod(jclass clazz, const char *methodName, Args &&...args) { - constexpr auto signature = QtJniTypes::methodSignature<Ret, Args...>(); - return callStaticMethod<Ret>(clazz, methodName, signature.data(), std::forward<Args>(args)...); + constexpr auto signature = QtJniTypes::methodSignature<ReturnType, Args...>(); + return callStaticMethod<ReturnType>(clazz, methodName, signature.data(), std::forward<Args>(args)...); } - template <typename Klass, typename Ret = void, typename ...Args + template <typename Klass, typename ReturnType = void, typename ...Args #ifndef Q_QDOC - , QtJniTypes::IfValidSignatureTypes<Ret, Args...> = true + , QtJniTypes::IfValidSignatureTypes<ReturnType, Args...> = true #endif > static auto callStaticMethod(const char *methodName, Args &&...args) { - JNIEnv *env = QJniEnvironment::getJniEnv(); + LocalFrame<ReturnType, Args...> frame; const jclass clazz = QJniObject::loadClass(QtJniTypes::Traits<Klass>::className().data(), - env); - const jmethodID id = clazz ? getMethodID(env, clazz, methodName, - QtJniTypes::methodSignature<Ret, Args...>().data(), true) + frame.jniEnv()); + const jmethodID id = clazz ? getMethodID(frame.jniEnv(), clazz, methodName, + QtJniTypes::methodSignature<ReturnType, Args...>().data(), true) : 0; - return callStaticMethod<Ret>(clazz, id, std::forward<Args>(args)...); + return callStaticMethod<ReturnType>(clazz, id, std::forward<Args>(args)...); } static QJniObject callStaticObjectMethod(const char *className, const char *methodName, @@ -315,7 +387,7 @@ public: { QtJniTypes::assertObjectType<Ret>(); constexpr auto signature = QtJniTypes::methodSignature<Ret, Args...>(); - LocalFrame<Args...> frame; + LocalFrame<QJniObject, Args...> frame; return frame.template convertFromJni<Ret>(callStaticObjectMethod(className, methodName, signature.data(), frame.convertToJni(std::forward<Args>(args))...)); } @@ -329,98 +401,95 @@ public: { QtJniTypes::assertObjectType<Ret>(); constexpr auto signature = QtJniTypes::methodSignature<Ret, Args...>(); - LocalFrame<Args...> frame; + LocalFrame<QJniObject, Args...> frame; return frame.template convertFromJni<Ret>(callStaticObjectMethod(clazz, methodName, signature.data(), - frame.convertToJni(std::forward<Args>(args))...)); + frame.convertToJni(std::forward<Args>(args))...)); } - template <typename T + template <typename Type #ifndef Q_QDOC - , QtJniTypes::IfValidFieldType<T> = true + , QtJniTypes::IfValidFieldType<Type> = true #endif > auto getField(const char *fieldName) const { - LocalFrame<T> frame(jniEnv()); + using T = typename QtJniTypes::Detail::CallerHandlesException<Type>::value_type; + LocalFrame<Type, T> frame(jniEnv()); + constexpr auto signature = QtJniTypes::fieldSignature<T>(); + jfieldID id = getCachedFieldID(frame.jniEnv(), fieldName, signature); + if constexpr (QtJniTypes::isObjectType<T>()) { - return frame.template convertFromJni<T>(getObjectField<T>(fieldName)); + return frame.makeResult(frame.template convertFromJni<T>(getObjectFieldImpl( + frame.jniEnv(), id)) + ); } else { T res{}; - constexpr auto signature = QtJniTypes::fieldSignature<T>(); - jfieldID id = getCachedFieldID(frame.jniEnv(), fieldName, signature); - if (id) { + if (id) getFieldForType<T>(frame.jniEnv(), res, object(), id); - if (frame.checkAndClearExceptions()) - res = {}; - } - return res; + return frame.makeResult(res); } } - template <typename T + template <typename Klass, typename T #ifndef Q_QDOC , QtJniTypes::IfValidFieldType<T> = true #endif > - static auto getStaticField(const char *className, const char *fieldName) + static auto getStaticField(const char *fieldName) { - LocalFrame<T> frame; - if constexpr (QtJniTypes::isObjectType<T>()) { - return frame.template convertFromJni<T>(getStaticObjectField<T>(className, fieldName)); - } else { - jclass clazz = QJniObject::loadClass(className, frame.jniEnv()); - if (!clazz) - return T{}; - return getStaticField<T>(clazz, fieldName); - } + return getStaticField<T>(QtJniTypes::Traits<Klass>::className(), fieldName); } template <typename T #ifndef Q_QDOC - , QtJniTypes::IfValidFieldType<T> = true + , std::enable_if_t<QtJniTypes::isObjectType<T>(), bool> = true #endif > - static auto getStaticField(jclass clazz, const char *fieldName) + QJniObject getObjectField(const char *fieldName) const { - LocalFrame<T> frame; - if constexpr (QtJniTypes::isObjectType<T>()) { - return frame.template convertFromJni<T>(getStaticObjectField<T>(clazz, fieldName)); - } else { - T res{}; - constexpr auto signature = QtJniTypes::fieldSignature<T>(); - jfieldID id = getFieldID(frame.jniEnv(), clazz, fieldName, signature, true); - if (id) { - getStaticFieldForType<T>(frame.jniEnv(), res, clazz, id); - if (frame.checkAndClearExceptions()) - res = {}; - } - return res; - } + constexpr auto signature = QtJniTypes::fieldSignature<T>(); + return getObjectField(fieldName, signature); } - template <typename Klass, typename T + QJniObject getObjectField(const char *fieldName, const char *signature) const; + + template <typename Type #ifndef Q_QDOC - , QtJniTypes::IfValidFieldType<T> = true + , QtJniTypes::IfValidFieldType<Type> = true #endif > - static auto getStaticField(const char *fieldName) + static auto getStaticField(const char *className, const char *fieldName) { - return getStaticField<T>(QtJniTypes::Traits<Klass>::className(), fieldName); + using T = typename QtJniTypes::Detail::CallerHandlesException<Type>::value_type; + LocalFrame<Type, T> frame; + jclass clazz = QJniObject::loadClass(className, frame.jniEnv()); + return getStaticField<Type>(clazz, fieldName); } - template <typename T + template <typename Type #ifndef Q_QDOC - , std::enable_if_t<QtJniTypes::isObjectType<T>(), bool> = true + , QtJniTypes::IfValidFieldType<Type> = true #endif > - QJniObject getObjectField(const char *fieldName) const + static auto getStaticField(jclass clazz, const char *fieldName) { + using T = typename QtJniTypes::Detail::CallerHandlesException<Type>::value_type; + LocalFrame<Type, T> frame; constexpr auto signature = QtJniTypes::fieldSignature<T>(); - return getObjectField(fieldName, signature); + jfieldID id = clazz ? getFieldID(frame.jniEnv(), clazz, fieldName, signature, true) + : nullptr; + if constexpr (QtJniTypes::isObjectType<T>()) { + return frame.makeResult(frame.template convertFromJni<T>(getStaticObjectFieldImpl( + frame.jniEnv(), clazz, id)) + ); + } else { + T res{}; + if (id) + getStaticFieldForType<T>(frame.jniEnv(), res, clazz, id); + return frame.makeResult(res); + } } - QJniObject getObjectField(const char *fieldName, const char *signature) const; - template <typename T #ifndef Q_QDOC , std::enable_if_t<QtJniTypes::isObjectType<T>(), bool> = true @@ -450,114 +519,122 @@ public: static QJniObject getStaticObjectField(jclass clazz, const char *fieldName, const char *signature); - template <typename T + template <typename Ret = void, typename Type #ifndef Q_QDOC - , QtJniTypes::IfValidFieldType<T> = true + , QtJniTypes::IfValidFieldType<Type> = true #endif > - void setField(const char *fieldName, T value) + auto setField(const char *fieldName, Type value) { + // handle old code explicitly specifying a non-return type for Ret + using T = std::conditional_t<!std::is_void_v<Ret> && !QtJniTypes::Detail::callerHandlesException<Ret>, + Ret, Type>; + LocalFrame<Ret, T> frame(jniEnv()); constexpr auto signature = QtJniTypes::fieldSignature<T>(); jfieldID id = getCachedFieldID(jniEnv(), fieldName, signature); - if (id) { + if (id) setFieldForType<T>(jniEnv(), object(), id, value); - QJniEnvironment::checkAndClearExceptions(jniEnv()); - } + return frame.makeResult(); } - template <typename T + template <typename Ret = void, typename Type #ifndef Q_QDOC - , QtJniTypes::IfValidFieldType<T> = true + , QtJniTypes::IfValidFieldType<Type> = true #endif > - void setField(const char *fieldName, const char *signature, T value) - { - jfieldID id = getCachedFieldID(jniEnv(), fieldName, signature); - if (id) { + auto setField(const char *fieldName, const char *signature, Type value) + { + // handle old code explicitly specifying a non-return type for Ret + using T = std::conditional_t<!std::is_void_v<Ret> && !QtJniTypes::Detail::callerHandlesException<Ret>, + Ret, Type>; + LocalFrame<Ret, T> frame(jniEnv()); + jfieldID id = getCachedFieldID(frame.jniEnv(), fieldName, signature); + if (id) setFieldForType<T>(jniEnv(), object(), id, value); - QJniEnvironment::checkAndClearExceptions(jniEnv()); - } + return frame.makeResult(); } - template <typename T + template <typename Ret = void, typename Type #ifndef Q_QDOC - , QtJniTypes::IfValidFieldType<T> = true + , QtJniTypes::IfValidFieldType<Type> = true #endif > - static void setStaticField(const char *className, const char *fieldName, T value) - { - LocalFrame<T> frame; - jclass clazz = QJniObject::loadClass(className, frame.jniEnv()); - if (!clazz) - return; - - constexpr auto signature = QtJniTypes::fieldSignature<T>(); - jfieldID id = getCachedFieldID(frame.jniEnv(), clazz, className, fieldName, - signature, true); - if (!id) - return; - - setStaticFieldForType<T>(frame.jniEnv(), clazz, id, value); - frame.checkAndClearExceptions(); + static auto setStaticField(const char *className, const char *fieldName, Type value) + { + // handle old code explicitly specifying a non-return type for Ret + using T = std::conditional_t<!std::is_void_v<Ret> && !QtJniTypes::Detail::callerHandlesException<Ret>, + Ret, Type>; + LocalFrame<Ret, T> frame; + if (jclass clazz = QJniObject::loadClass(className, frame.jniEnv())) { + constexpr auto signature = QtJniTypes::fieldSignature<q20::remove_cvref_t<T>>(); + jfieldID id = getCachedFieldID(frame.jniEnv(), clazz, className, fieldName, + signature, true); + if (id) + setStaticFieldForType<T>(frame.jniEnv(), clazz, id, value); + } + return frame.makeResult(); } - template <typename T + template <typename Ret = void, typename Type #ifndef Q_QDOC - , QtJniTypes::IfValidFieldType<T> = true + , QtJniTypes::IfValidFieldType<Type> = true #endif > - static void setStaticField(const char *className, const char *fieldName, - const char *signature, T value) - { - JNIEnv *env = QJniEnvironment::getJniEnv(); - jclass clazz = QJniObject::loadClass(className, env); - - if (!clazz) - return; - - jfieldID id = getCachedFieldID(env, clazz, className, fieldName, - signature, true); - if (id) { - setStaticFieldForType<T>(env, clazz, id, value); - QJniEnvironment::checkAndClearExceptions(env); + static auto setStaticField(const char *className, const char *fieldName, + const char *signature, Type value) + { + // handle old code explicitly specifying a non-return type for Ret + using T = std::conditional_t<!std::is_void_v<Ret> && !QtJniTypes::Detail::callerHandlesException<Ret>, + Ret, Type>; + LocalFrame<Ret, T> frame; + if (jclass clazz = QJniObject::loadClass(className, frame.jniEnv())) { + jfieldID id = getCachedFieldID(frame.jniEnv(), clazz, className, fieldName, + signature, true); + if (id) + setStaticFieldForType<T>(frame.jniEnv(), clazz, id, value); } + return frame.makeResult(); } - template <typename T + template <typename Ret = void, typename Type #ifndef Q_QDOC - , QtJniTypes::IfValidFieldType<T> = true + , QtJniTypes::IfValidFieldType<Type> = true #endif > - static void setStaticField(jclass clazz, const char *fieldName, - const char *signature, T value) + static auto setStaticField(jclass clazz, const char *fieldName, + const char *signature, Type value) { - JNIEnv *env = QJniEnvironment::getJniEnv(); - jfieldID id = getFieldID(env, clazz, fieldName, signature, true); + // handle old code explicitly specifying a non-return type for Ret + using T = std::conditional_t<!std::is_void_v<Ret> && !QtJniTypes::Detail::callerHandlesException<Ret>, + Ret, Type>; + LocalFrame<Ret, T> frame; + jfieldID id = getFieldID(frame.jniEnv(), clazz, fieldName, signature, true); - if (id) { - setStaticFieldForType<T>(env, clazz, id, value); - QJniEnvironment::checkAndClearExceptions(env); - } + if (id) + setStaticFieldForType<T>(frame.jniEnv(), clazz, id, value); + return frame.makeResult(); } - template <typename T + template <typename Ret = void, typename Type #ifndef Q_QDOC - , QtJniTypes::IfValidFieldType<T> = true + , QtJniTypes::IfValidFieldType<Type> = true #endif > - static void setStaticField(jclass clazz, const char *fieldName, T value) + static auto setStaticField(jclass clazz, const char *fieldName, Type value) { - setStaticField(clazz, fieldName, QtJniTypes::fieldSignature<T>(), value); + return setStaticField<Ret, Type>(clazz, fieldName, + QtJniTypes::fieldSignature<q20::remove_cvref_t<Type>>(), + value); } - template <typename Klass, typename T + template <typename Klass, typename Ret = void, typename Type #ifndef Q_QDOC - , QtJniTypes::IfValidFieldType<T> = true + , QtJniTypes::IfValidFieldType<Type> = true #endif > - static void setStaticField(const char *fieldName, T value) + static auto setStaticField(const char *fieldName, Type value) { - setStaticField(QtJniTypes::Traits<Klass>::className(), fieldName, value); + return setStaticField<Ret, Type>(QtJniTypes::Traits<Klass>::className(), fieldName, value); } static QJniObject fromString(const QString &string); @@ -583,6 +660,7 @@ protected: private: static jclass loadClass(const QByteArray &className, JNIEnv *env); + static jclass loadClassKeepExceptions(const QByteArray &className, JNIEnv *env); #if QT_CORE_REMOVED_SINCE(6, 7) // these need to stay in the ABI as they were used in inline methods before 6.7 @@ -620,12 +698,25 @@ private: template<typename T> static constexpr void callMethodForType(JNIEnv *env, T &res, jobject obj, jmethodID id, ...) { + if (!id) + return; + va_list args = {}; va_start(args, id); QtJniTypes::Caller<T>::callMethodForType(env, res, obj, id, args); va_end(args); } + jobject callObjectMethodImpl(jmethodID method, ...) const + { + va_list args; + va_start(args, method); + jobject res = method ? jniEnv()->CallObjectMethodV(javaObject(), method, args) + : nullptr; + va_end(args); + return res; + } + template<typename T> static constexpr void callStaticMethodForType(JNIEnv *env, T &res, jclass clazz, jmethodID id, ...) @@ -652,6 +743,9 @@ private: template<typename T> static constexpr void getFieldForType(JNIEnv *env, T &res, jobject obj, jfieldID id) { + if (!id) + return; + QtJniTypes::Caller<T>::getFieldForType(env, res, obj, id); } @@ -661,22 +755,42 @@ private: QtJniTypes::Caller<T>::getStaticFieldForType(env, res, clazz, id); } - template<typename T> - static constexpr void setFieldForType(JNIEnv *env, jobject obj, jfieldID id, T value) + template<typename Type> + static constexpr void setFieldForType(JNIEnv *env, jobject obj, jfieldID id, Type value) { + if (!id) + return; + + using T = q20::remove_cvref_t<Type>; if constexpr (QtJniTypes::isObjectType<T>()) { - LocalFrame<T> frame(env); + LocalFrame<T, T> frame(env); env->SetObjectField(obj, id, static_cast<jobject>(frame.convertToJni(value))); } else { - QtJniTypes::Caller<T>::setFieldForType(env, obj, id, value); + using ValueType = typename QtJniTypes::Detail::CallerHandlesException<T>::value_type; + QtJniTypes::Caller<ValueType>::setFieldForType(env, obj, id, value); } } - template<typename T> - static constexpr void setStaticFieldForType(JNIEnv *env, jclass clazz, jfieldID id, T value) + jobject getObjectFieldImpl(JNIEnv *env, jfieldID field) const { + return field ? env->GetObjectField(javaObject(), field) : nullptr; + } + + static jobject getStaticObjectFieldImpl(JNIEnv *env, jclass clazz, jfieldID field) + { + return clazz && field ? env->GetStaticObjectField(clazz, field) + : nullptr; + } + + template<typename Type> + static constexpr void setStaticFieldForType(JNIEnv *env, jclass clazz, jfieldID id, Type value) + { + if (!clazz || !id) + return; + + using T = q20::remove_cvref_t<Type>; if constexpr (QtJniTypes::isObjectType<T>()) { - LocalFrame<T> frame(env); + LocalFrame<T, T> frame(env); env->SetStaticObjectField(clazz, id, static_cast<jobject>(frame.convertToJni(value))); } else { QtJniTypes::Caller<T>::setStaticFieldForType(env, clazz, id, value); @@ -698,7 +812,7 @@ inline bool operator!=(const QJniObject &obj1, const QJniObject &obj2) } namespace QtJniTypes { -struct QT_TECH_PREVIEW_API JObjectBase +struct JObjectBase { operator QJniObject() const { return m_object; } @@ -727,7 +841,7 @@ protected: }; template<typename Type> -class QT_TECH_PREVIEW_API JObject : public JObjectBase +class JObject : public JObjectBase { public: using Class = Type; @@ -771,6 +885,13 @@ public: return JObject(QJniObject::fromLocalRef(lref)); } +#ifdef Q_QDOC // from JObjectBase, which we don't document + bool isValid() const; + jclass objectClass() const; + QString toString() const; + template <typename T = jobject> object() const; +#endif + static bool registerNativeMethods(std::initializer_list<JNINativeMethod> methods) { QJniEnvironment env; @@ -797,14 +918,14 @@ public: { return QJniObject::getStaticField<Class, T>(field); } - template <typename T + template <typename Ret = void, typename T #ifndef Q_QDOC , QtJniTypes::IfValidFieldType<T> = true #endif > - static void setStaticField(const char *field, T &&value) + static auto setStaticField(const char *field, T &&value) { - QJniObject::setStaticField<Class, T>(field, std::forward<T>(value)); + return QJniObject::setStaticField<Class, Ret, T>(field, std::forward<T>(value)); } // keep only these overloads, the rest is made private @@ -827,14 +948,14 @@ public: return m_object.getField<T>(fieldName); } - template <typename T + template <typename Ret = void, typename T #ifndef Q_QDOC , QtJniTypes::IfValidFieldType<T> = true #endif > - void setField(const char *fieldName, T &&value) + auto setField(const char *fieldName, T &&value) { - m_object.setField(fieldName, std::forward<T>(value)); + return m_object.setField<Ret>(fieldName, std::forward<T>(value)); } QByteArray className() const { @@ -911,6 +1032,37 @@ struct Traits<QString> } }; +template <typename T> +struct Traits<T, std::enable_if_t<QtJniTypes::Detail::callerHandlesException<T>>> +{ + static constexpr auto className() + { + return Traits<typename T::value_type>::className(); + } + + static constexpr auto signature() + { + return Traits<typename T::value_type>::signature(); + } +}; + +} + +template <typename ReturnType, typename ...Args> +template <typename T> +auto QtJniTypes::Detail::LocalFrame<ReturnType, Args...>::convertFromJni(jobject object) +{ + // If the caller wants to handle exceptions through a std::expected-like + // type, then we cannot turn the jobject into a QJniObject, as a + // std::expected<jobject, jthrowable> cannot be constructed from a QJniObject. + // The caller will have to take care of this themselves, by asking for a + // std::expected<QJniObject, ...>, or (typically) using a declared JNI class + // or implicitly supported Qt type (QString or array type). + if constexpr (callerHandlesException<ReturnType> && + std::is_base_of_v<std::remove_pointer_t<jobject>, std::remove_pointer_t<T>>) + return static_cast<T>(object); + else + return convertFromJni<T>(object ? QJniObject::fromLocalRef(object) : QJniObject()); } QT_END_NAMESPACE diff --git a/src/corelib/kernel/qjnitypes.h b/src/corelib/kernel/qjnitypes.h index 935388311a5..8ee367d188f 100644 --- a/src/corelib/kernel/qjnitypes.h +++ b/src/corelib/kernel/qjnitypes.h @@ -19,12 +19,11 @@ QT_BEGIN_NAMESPACE -// QT_TECH_PREVIEW_API #define Q_DECLARE_JNI_TYPE_HELPER(Type) \ struct Type##Tag { explicit Type##Tag() = default; }; \ using Type = JObject<Type##Tag>; \ -// QT_TECH_PREVIEW_API +// internal - Q_DECLARE_JNI_CLASS is the public macro #define Q_DECLARE_JNI_TYPE(Type, Signature) \ namespace QtJniTypes { \ Q_DECLARE_JNI_TYPE_HELPER(Type) \ diff --git a/src/corelib/kernel/qmetaassociation.cpp b/src/corelib/kernel/qmetaassociation.cpp index dc239424e6d..5eae6658a57 100644 --- a/src/corelib/kernel/qmetaassociation.cpp +++ b/src/corelib/kernel/qmetaassociation.cpp @@ -1,7 +1,7 @@ // Copyright (C) 2025 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -#include <QtCore/qmetacontainer.h> +#include <QtCore/qmetaassociation.h> #include <QtCore/qmetatype.h> QT_BEGIN_NAMESPACE @@ -287,7 +287,6 @@ QMetaType QMetaAssociation::mappedMetaType() const Returns \c true if the QMetaAssociation \a lhs represents the same container type as the QMetaAssociation \a rhs, otherwise returns \c false. */ - /*! \fn bool QMetaAssociation::operator!=(const QMetaAssociation &lhs, const QMetaAssociation &rhs) @@ -295,5 +294,197 @@ QMetaType QMetaAssociation::mappedMetaType() const type than the QMetaAssociation \a rhs, otherwise returns \c false. */ +/*! + \class QMetaAssociation::Iterable + \inherits QIterable + \since 6.11 + \inmodule QtCore + \brief QMetaAssociation::Iterable is an iterable interface for an associative container in a QVariant. + + This class allows several methods of accessing the elements of an + associative container held within a QVariant. An instance of + QMetaAssociation::Iterable can be extracted from a QVariant if it can be + converted to a QVariantHash or QVariantMap or if a custom mutable view has + been registered. + + \snippet code/src_corelib_kernel_qvariant.cpp 10 + + The container itself is not copied before iterating over it. + + \sa QVariant +*/ + +/*! + \typealias QMetaAssociation::Iterable::RandomAccessIterator + Exposes an iterator using std::random_access_iterator_tag. +*/ + +/*! + \typealias QMetaAssociation::Iterable::BidirectionalIterator + Exposes an iterator using std::bidirectional_iterator_tag. +*/ + +/*! + \typealias QMetaAssociation::Iterable::ForwardIterator + Exposes an iterator using std::forward_iterator_tag. +*/ + +/*! + \typealias QMetaAssociation::Iterable::InputIterator + Exposes an iterator using std::input_iterator_tag. +*/ + +/*! + \typealias QMetaAssociation::Iterable::RandomAccessConstIterator + Exposes a const_iterator using std::random_access_iterator_tag. +*/ + +/*! + \typealias QMetaAssociation::Iterable::BidirectionalConstIterator + Exposes a const_iterator using std::bidirectional_iterator_tag. +*/ + +/*! + \typealias QMetaAssociation::Iterable::ForwardConstIterator + Exposes a const_iterator using std::forward_iterator_tag. +*/ + +/*! + \typealias QMetaAssociation::Iterable::InputConstIterator + Exposes a const_iterator using std::input_iterator_tag. +*/ + +/*! + \class QMetaAssociation::Iterable::ConstIterator + \inherits QConstIterator + \since 6.11 + \inmodule QtCore + \brief QMetaAssociation::Iterable::ConstIterator allows iteration over a container in a QVariant. + + A QMetaAssociation::Iterable::ConstIterator can only be created by a + QMetaAssociation::Iterable instance, and can be used in a way similar to + other stl-style iterators. + + \snippet code/src_corelib_kernel_qvariant.cpp 10 + + \sa QMetaAssociation::Iterable +*/ + +/*! + \class QMetaAssociation::Iterable::Iterator + \inherits QIterator + \since 6.11 + \inmodule QtCore + \brief The QMetaAssociation::Iterable::Iterator allows iteration over a container in a QVariant. + + A QMetaAssociation::Iterable::Iterator can only be created by a + QMetaAssociation::Iterable instance, and can be used in a way similar to + other stl-style iterators. + + \sa QMetaAssociation::Iterable +*/ + +/*! + \fn QMetaAssociation::Iterable::ConstIterator QMetaAssociation::Iterable::find(const QVariant &key) const + Retrieves a ConstIterator pointing to the element at the given \a key, or + the end of the container if that key does not exist. If the \a key isn't + convertible to the expected type, the end of the container is returned. + */ + +/*! + \fn QMetaAssociation::Iterable::Iterator QMetaAssociation::Iterable::mutableFind(const QVariant &key) + Retrieves an iterator pointing to the element at the given \a key, or + the end of the container if that key does not exist. If the \a key isn't + convertible to the expected type, the end of the container is returned. + */ + +/*! + \fn bool QMetaAssociation::Iterable::containsKey(const QVariant &key) const + Returns \c true if the container has an entry with the given \a key, or + \c false otherwise. If the \a key isn't convertible to the expected type, + \c false is returned. + */ + +/*! + \fn void QMetaAssociation::Iterable::insertKey(const QVariant &key) + Inserts a new entry with the given \a key, or resets the mapped value of + any existing entry with the given \a key to the default constructed + mapped value. The \a key is coerced to the expected type: If it isn't + convertible, a default value is inserted. + */ + +/*! + \fn void QMetaAssociation::Iterable::removeKey(const QVariant &key) + Removes the entry with the given \a key from the container. The \a key is + coerced to the expected type: If it isn't convertible, the default value + is removed. + */ + +/*! + \fn QVariant QMetaAssociation::Iterable::value(const QVariant &key) const + Retrieves the mapped value at the given \a key, or a QVariant of a + default-constructed instance of the mapped type, if the key does not + exist. If the \a key is not convertible to the key type, the mapped value + associated with the default-constructed key is returned. + */ + +/*! + \fn void QMetaAssociation::Iterable::setValue(const QVariant &key, const QVariant &mapped) + Sets the mapped value associated with \a key to \a mapped, if possible. + Inserts a new entry if none exists yet, for the given \a key. If the + \a key is not convertible to the key type, the value for the + default-constructed key type is overwritten. + */ + + +/*! + \fn QVariant QMetaAssociation::Iterable::Iterator::key() const + Returns the key this iterator points to. +*/ + +/*! + \fn QVariant::Reference<QMetaAssociation::Iterable::Iterator> QMetaAssociation::Iterable::Iterator::value() const + Returns the mapped value this iterator points to. If the container does not + provide a mapped value (for example a set), returns an invalid + QVariant::Reference. +*/ + +/*! + \fn QVariant::Reference<QMetaAssociation::Iterable::Iterator> QMetaAssociation::Iterable::Iterator::operator*() const + Returns the current item, converted to a QVariant::Reference. The resulting + QVariant::Reference resolves to the mapped value if there is one, or to the + key value if not. +*/ + +/*! + \fn QVariant::Pointer<QMetaAssociation::Iterable::Iterator> QMetaAssociation::Iterable::Iterator::operator->() const + Returns the current item, converted to a QVariant::Pointer. The resulting + QVariant::Pointer resolves to the mapped value if there is one, or to the + key value if not. +*/ + +/*! + \fn QVariant QMetaAssociation::Iterable::ConstIterator::key() const + Returns the key this iterator points to. +*/ + +/*! + \fn QVariant QMetaAssociation::Iterable::ConstIterator::value() const + Returns the mapped value this iterator points to, or an invalid QVariant if + there is no mapped value. +*/ + +/*! + \fn QVariant QMetaAssociation::Iterable::ConstIterator::operator*() const + Returns the current item, converted to a QVariant. The returned value is the + mapped value at the current iterator if there is one, or otherwise the key. +*/ + +/*! + \fn QVariant::ConstPointer<QMetaAssociation::Iterable::ConstIterator> QMetaAssociation::Iterable::ConstIterator::operator->() const + Returns the current item, converted to a QVariant::ConstPointer. The + QVariant::ConstPointer will resolve to the mapped value at the current + iterator if there is one, or otherwise the key. +*/ QT_END_NAMESPACE diff --git a/src/corelib/kernel/qmetaassociation.h b/src/corelib/kernel/qmetaassociation.h new file mode 100644 index 00000000000..d481ae91079 --- /dev/null +++ b/src/corelib/kernel/qmetaassociation.h @@ -0,0 +1,266 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QMETAASSOCIATION_H +#define QMETAASSOCIATION_H + +#if 0 +#pragma qt_class(QMetaAssociation) +#endif + +#include <QtCore/qiterable.h> +#include <QtCore/qiterable_impl.h> +#include <QtCore/qmetacontainer.h> +#include <QtCore/qvariant.h> + +QT_BEGIN_NAMESPACE + +namespace QtMetaContainerPrivate { + +class AssociativeIterator : public QIterator<QMetaAssociation> +{ +public: + using key_type = QVariant; + using mapped_type = QVariant; + using reference = QVariant::Reference<AssociativeIterator>; + using pointer = QVariant::Pointer<AssociativeIterator>; + + static constexpr bool CanNoexceptAssignQVariant = false; + static constexpr bool CanNoexceptConvertToQVariant = false; + + AssociativeIterator(QIterator &&it) : QIterator(std::move(it)) {} + + key_type key() const + { + const QMetaAssociation meta = metaContainer(); + return QIterablePrivate::retrieveElement(meta.keyMetaType(), [&](void *dataPtr) { + meta.keyAtIterator(constIterator(), dataPtr); + }); + } + reference value() const { return operator*(); } + + reference operator*() const { return reference(*this); } + pointer operator->() const { return pointer(*this); } +}; + +class AssociativeConstIterator : public QConstIterator<QMetaAssociation> +{ +public: + using key_type = QVariant; + using mapped_type = QVariant; + using reference = QVariant::ConstReference<AssociativeConstIterator>; + using pointer = QVariant::ConstPointer<AssociativeConstIterator>; + + static constexpr bool CanNoexceptConvertToQVariant = false; + + AssociativeConstIterator(QConstIterator &&it) : QConstIterator(std::move(it)) {} + + key_type key() const + { + const QMetaAssociation meta = metaContainer(); + return QIterablePrivate::retrieveElement(meta.keyMetaType(), [&](void *dataPtr) { + meta.keyAtConstIterator(constIterator(), dataPtr); + }); + } + + mapped_type value() const { return operator*(); } + + mapped_type operator*() const; + pointer operator->() const { return pointer(*this); } +}; + +} // namespace QtMetaContainerPrivate + +namespace QtPrivate { + +template<typename Referred> +QVariant associativeIteratorToVariant(const Referred &referred) +{ + const auto metaAssociation = referred.metaContainer(); + const QMetaType metaType(metaAssociation.mappedMetaType()); + if (metaType.isValid(QT6_CALL_NEW_OVERLOAD)) { + return QIterablePrivate::retrieveElement(metaType, [&](void *dataPtr) { + metaAssociation.mappedAtConstIterator(referred.constIterator(), dataPtr); + }); + } + + return QIterablePrivate::retrieveElement(metaType, [&](void *dataPtr) { + metaAssociation.keyAtConstIterator(referred.constIterator(), dataPtr); + }); +} + +} // namespace QtPrivate + +template<> +inline QVariant::Reference<QtMetaContainerPrivate::AssociativeIterator>::operator QVariant() const +{ + return QtPrivate::associativeIteratorToVariant(m_referred); +} + +template<> +inline QVariant::Reference<QtMetaContainerPrivate::AssociativeIterator> & +QVariant::Reference<QtMetaContainerPrivate::AssociativeIterator>::operator=(const QVariant &value) +{ + const auto metaAssociation = m_referred.metaContainer(); + const QMetaType metaType(metaAssociation.mappedMetaType()); + if (!metaType.isValid(QT6_CALL_NEW_OVERLOAD)) + return *this; + + QtPrivate::QVariantTypeCoercer coercer; + metaAssociation.setMappedAtIterator( + m_referred.constIterator(), coercer.coerce(value, metaType)); + return *this; +} + +template<> +inline QVariant::ConstReference<QtMetaContainerPrivate::AssociativeConstIterator>::operator QVariant() const +{ + return QtPrivate::associativeIteratorToVariant(m_referred); +} + +namespace QtMetaContainerPrivate { +inline AssociativeConstIterator::mapped_type AssociativeConstIterator::operator*() const +{ + return reference(*this); +} + +class Association : public QIterable<QMetaAssociation> +{ +public: + using Iterator + = QTaggedIterator<AssociativeIterator, void>; + using RandomAccessIterator + = QTaggedIterator<AssociativeIterator, std::random_access_iterator_tag>; + using BidirectionalIterator + = QTaggedIterator<AssociativeIterator, std::bidirectional_iterator_tag>; + using ForwardIterator + = QTaggedIterator<AssociativeIterator, std::forward_iterator_tag>; + using InputIterator + = QTaggedIterator<AssociativeIterator, std::input_iterator_tag>; + + using ConstIterator + = QTaggedIterator<AssociativeConstIterator, void>; + using RandomAccessConstIterator + = QTaggedIterator<AssociativeConstIterator, std::random_access_iterator_tag>; + using BidirectionalConstIterator + = QTaggedIterator<AssociativeConstIterator, std::bidirectional_iterator_tag>; + using ForwardConstIterator + = QTaggedIterator<AssociativeConstIterator, std::forward_iterator_tag>; + using InputConstIterator + = QTaggedIterator<AssociativeConstIterator, std::input_iterator_tag>; + + using iterator = Iterator; + using const_iterator = ConstIterator; + + template<class T> + Association(const T *p) : QIterable(QMetaAssociation::fromContainer<T>(), p) {} + + template<class T> + Association(T *p) : QIterable(QMetaAssociation::fromContainer<T>(), p) {} + + Association() : QIterable(QMetaAssociation(), nullptr) {} + + template<typename Pointer> + Association(const QMetaAssociation &metaAssociation, Pointer iterable) + : QIterable(metaAssociation, iterable) + { + } + + Association(const QMetaAssociation &metaAssociation, QMetaType metaType, void *iterable) + : QIterable(metaAssociation, metaType.alignOf(), iterable) + { + } + + Association(const QMetaAssociation &metaAssociation, QMetaType metaType, const void *iterable) + : QIterable(metaAssociation, metaType.alignOf(), iterable) + { + } + + Association(QIterable<QMetaAssociation> &&other) + : QIterable(std::move(other)) + {} + + Association &operator=(QIterable<QMetaAssociation> &&other) + { + QIterable::operator=(std::move(other)); + return *this; + } + + ConstIterator begin() const { return constBegin(); } + ConstIterator end() const { return constEnd(); } + + ConstIterator constBegin() const { return ConstIterator(QIterable::constBegin()); } + ConstIterator constEnd() const { return ConstIterator(QIterable::constEnd()); } + + Iterator mutableBegin() { return Iterator(QIterable::mutableBegin()); } + Iterator mutableEnd() { return Iterator(QIterable::mutableEnd()); } + + ConstIterator find(const QVariant &key) const + { + const QMetaAssociation meta = metaContainer(); + QtPrivate::QVariantTypeCoercer coercer; + if (const void *keyData = coercer.convert(key, meta.keyMetaType())) { + return ConstIterator(QConstIterator<QMetaAssociation>( + this, meta.createConstIteratorAtKey(constIterable(), keyData))); + } + return constEnd(); + } + + ConstIterator constFind(const QVariant &key) const { return find(key); } + + Iterator mutableFind(const QVariant &key) + { + const QMetaAssociation meta = metaContainer(); + QtPrivate::QVariantTypeCoercer coercer; + if (const void *keyData = coercer.convert(key, meta.keyMetaType())) + return Iterator(QIterator(this, meta.createIteratorAtKey(mutableIterable(), keyData))); + return mutableEnd(); + } + + bool containsKey(const QVariant &key) const + { + const QMetaAssociation meta = metaContainer(); + QtPrivate::QVariantTypeCoercer keyCoercer; + if (const void *keyData = keyCoercer.convert(key, meta.keyMetaType())) + return meta.containsKey(constIterable(), keyData); + return false; + } + + void insertKey(const QVariant &key) + { + const QMetaAssociation meta = metaContainer(); + QtPrivate::QVariantTypeCoercer keyCoercer; + meta.insertKey(mutableIterable(), keyCoercer.coerce(key, meta.keyMetaType())); + } + + void removeKey(const QVariant &key) + { + const QMetaAssociation meta = metaContainer(); + QtPrivate::QVariantTypeCoercer keyCoercer; + meta.removeKey(mutableIterable(), keyCoercer.coerce(key, meta.keyMetaType())); + } + + QVariant value(const QVariant &key) const + { + const QMetaAssociation meta = metaContainer(); + return QIterablePrivate::retrieveElement(meta.mappedMetaType(), [&](void *dataPtr) { + QtPrivate::QVariantTypeCoercer coercer; + meta.mappedAtKey(constIterable(), coercer.coerce(key, meta.keyMetaType()), dataPtr); + }); + } + + void setValue(const QVariant &key, const QVariant &mapped) + { + const QMetaAssociation meta = metaContainer(); + QtPrivate::QVariantTypeCoercer keyCoercer; + QtPrivate::QVariantTypeCoercer mappedCoercer; + meta.setMappedAtKey(mutableIterable(), keyCoercer.coerce(key, meta.keyMetaType()), + mappedCoercer.coerce(mapped, meta.mappedMetaType())); + } +}; + +} // namespace QtMetaContainerPrivate + +QT_END_NAMESPACE + +#endif // QMETAASSOCIATION_H diff --git a/src/corelib/kernel/qmetacontainer.cpp b/src/corelib/kernel/qmetacontainer.cpp index 4b4ea06d8b9..6173198a972 100644 --- a/src/corelib/kernel/qmetacontainer.cpp +++ b/src/corelib/kernel/qmetacontainer.cpp @@ -210,7 +210,7 @@ void QMetaContainer::destroyIterator(const void *iterator) const */ bool QMetaContainer::compareIterator(const void *i, const void *j) const { - return hasIterator() ? d_ptr->compareIteratorFn(i, j) : false; + return i == j || (hasIterator() && d_ptr->compareIteratorFn(i, j)); } /*! @@ -249,7 +249,7 @@ void QMetaContainer::advanceIterator(void *iterator, qsizetype step) const */ qsizetype QMetaContainer::diffIterator(const void *i, const void *j) const { - return hasIterator() ? d_ptr->diffIteratorFn(i, j) : 0; + return (i != j && hasIterator()) ? d_ptr->diffIteratorFn(i, j) : 0; } /*! @@ -327,7 +327,7 @@ void QMetaContainer::destroyConstIterator(const void *iterator) const */ bool QMetaContainer::compareConstIterator(const void *i, const void *j) const { - return hasConstIterator() ? d_ptr->compareConstIteratorFn(i, j) : false; + return i == j || (hasConstIterator() && d_ptr->compareConstIteratorFn(i, j)); } /*! @@ -366,7 +366,7 @@ void QMetaContainer::advanceConstIterator(void *iterator, qsizetype step) const */ qsizetype QMetaContainer::diffConstIterator(const void *i, const void *j) const { - return hasConstIterator() ? d_ptr->diffConstIteratorFn(i, j) : 0; + return (i != j && hasConstIterator()) ? d_ptr->diffConstIteratorFn(i, j) : 0; } QT_END_NAMESPACE diff --git a/src/corelib/kernel/qmetacontainer.h b/src/corelib/kernel/qmetacontainer.h index 1bed7f9f7b3..c9d3a6bf9c6 100644 --- a/src/corelib/kernel/qmetacontainer.h +++ b/src/corelib/kernel/qmetacontainer.h @@ -22,6 +22,11 @@ constexpr const QMetaTypeInterface *qMetaTypeInterfaceForType(); namespace QtMetaContainerPrivate { +class Sequence; +class SequentialIterator; +class Association; +class AssociativeIterator; + enum IteratorCapability : quint8 { InputCapability = 1 << 0, ForwardCapability = 1 << 1, @@ -922,9 +927,67 @@ protected: const QtMetaContainerPrivate::QMetaContainerInterface *d_ptr = nullptr; }; +// ### Qt7: Move this to qmetasequence.h, including QtMetaContainerPrivate parts above. class Q_CORE_EXPORT QMetaSequence : public QMetaContainer { public: +#ifdef Q_QDOC + class Iterable : public QIterable<QMetaSequence> + { + public: + class Iterator : public QIterator<QMetaSequence> + { + public: + QVariant::Reference<Iterator> operator*() const; + QVariant::Pointer<Iterator> operator->() const; + QVariant::Reference<Iterator> operator[](qsizetype n) const; + }; + + class ConstIterator : public QConstIterator<QMetaSequence> + { + public: + QVariant operator*() const; + QVariant::ConstPointer<ConstIterator> operator->() const; + QVariant operator[](qsizetype n) const; + }; + + using RandomAccessIterator = Iterator; + using BidirectionalIterator = Iterator; + using ForwardIterator = Iterator; + using InputIterator = Iterator; + + using RandomAccessConstIterator = ConstIterator; + using BidirectionalConstIterator = ConstIterator; + using ForwardConstIterator = ConstIterator; + using InputConstIterator = ConstIterator; + + ConstIterator begin() const; + ConstIterator end() const; + + ConstIterator constBegin() const; + ConstIterator constEnd() const; + + Iterator mutableBegin(); + Iterator mutableEnd(); + + QVariant at(qsizetype idx) const; + void set(qsizetype idx, const QVariant &value); + void append(const QVariant &value); + void prepend(const QVariant &value); + void removeLast(); + void removeFirst(); + +#if QT_DEPRECATED_SINCE(6, 11) + enum Position: quint8 { Unspecified, AtBegin, AtEnd }; + void addValue(const QVariant &value, Position position = Unspecified); + void removeValue(Position position = Unspecified); + QMetaType valueMetaType() const; +#endif // QT_DEPRECATED_SINCE(6, 11) + }; +#else + using Iterable = QtMetaContainerPrivate::Sequence; +#endif + QMetaSequence() = default; explicit QMetaSequence(const QtMetaContainerPrivate::QMetaSequenceInterface *d) : QMetaContainer(d) {} @@ -999,9 +1062,67 @@ private: } }; +// ### Qt7: Move this to qmetaassociation.h, including QtMetaContainerPrivate parts above. class Q_CORE_EXPORT QMetaAssociation : public QMetaContainer { public: +#ifdef Q_QDOC + class Iterable : public QIterable<QMetaAssociation> + { + public: + class Iterator : public QIterator<QMetaAssociation> + { + public: + QVariant key() const; + QVariant value() const; + + QVariant::Reference<Iterator> operator*() const; + QVariant::Pointer<Iterator> operator->() const; + }; + + class ConstIterator : public QConstIterator<QMetaAssociation> + { + public: + QVariant key() const; + QVariant value() const; + + QVariant operator*() const; + QVariant::ConstPointer<ConstIterator> operator->() const; + }; + + using RandomAccessIterator = Iterator; + using BidirectionalIterator = Iterator; + using ForwardIterator = Iterator; + using InputIterator = Iterator; + + using RandomAccessConstIterator = ConstIterator; + using BidirectionalConstIterator = ConstIterator; + using ForwardConstIterator = ConstIterator; + using InputConstIterator = ConstIterator; + + ConstIterator begin() const; + ConstIterator end() const; + + ConstIterator constBegin() const; + ConstIterator constEnd() const; + + Iterator mutableBegin(); + Iterator mutableEnd(); + + ConstIterator find(const QVariant &key) const; + ConstIterator constFind(const QVariant &key) const; + Iterator mutableFind(const QVariant &key); + + bool containsKey(const QVariant &key) const; + void insertKey(const QVariant &key); + void removeKey(const QVariant &key); + QVariant value(const QVariant &key) const; + void setValue(const QVariant &key, const QVariant &mapped); + }; +#else + using Iterable = QtMetaContainerPrivate::Association; +#endif + QMetaAssociation() = default; explicit QMetaAssociation(const QtMetaContainerPrivate::QMetaAssociationInterface *d) : QMetaContainer(d) {} diff --git a/src/corelib/kernel/qmetaobject.cpp b/src/corelib/kernel/qmetaobject.cpp index a5d34eac707..c7e50788b45 100644 --- a/src/corelib/kernel/qmetaobject.cpp +++ b/src/corelib/kernel/qmetaobject.cpp @@ -469,6 +469,33 @@ QMetaType QMetaObject::metaType() const } } +static inline QByteArrayView objectMetaObjectHash(const QMetaObject *m) +{ + // metaObjectHash didn't exist before revision 14 + if (QT_VERSION < QT_VERSION_CHECK(7, 0, 0) && priv(m->d.data)->revision < 14) + return {}; + const auto index = priv(m->d.data)->metaObjectHashIndex; + if (index == -1) + return {}; + return stringDataView(m, index); +} + +/*! + \since 6.11 + + Returns the revisioned hash of the contents of this QMetaObject or nullptr. + + The hash has the following format <hash_revision>$<hash_b64>, where + hash_revision is an integer and hash_b64 is the base64 encoding of the + hash. + + Note that only hashes of the same revision should be compared. +*/ +const char *QMetaObject::metaObjectHash() const +{ + return objectMetaObjectHash(this).constData(); +} + /*! Returns the method offset for this class; i.e. the index position of this class's first member function. @@ -4405,6 +4432,34 @@ bool QMetaProperty::isFinal() const } /*! + \since 6.11 + Returns \c true if the property is virtual; otherwise returns \c false. + + A property is virtual if the \c{Q_PROPERTY()}'s \c VIRTUAL attribute + is set. +*/ +bool QMetaProperty::isVirtual() const +{ + if (!mobj) + return false; + return data.flags() & Virtual; +} + +/*! + \since 6.11 + Returns \c true if the property does override; otherwise returns \c false. + + A property does override if the \c{Q_PROPERTY()}'s \c OVERRIDE attribute + is set. +*/ +bool QMetaProperty::isOverride() const +{ + if (!mobj) + return false; + return data.flags() & Override; +} + +/*! \since 5.15 Returns \c true if the property is required; otherwise returns \c false. diff --git a/src/corelib/kernel/qmetaobject.h b/src/corelib/kernel/qmetaobject.h index 0f793ca753b..ff3cc751c3a 100644 --- a/src/corelib/kernel/qmetaobject.h +++ b/src/corelib/kernel/qmetaobject.h @@ -365,6 +365,8 @@ public: bool isUser() const; bool isConstant() const; bool isFinal() const; + bool isVirtual() const; + bool isOverride() const; bool isRequired() const; bool isBindable() const; diff --git a/src/corelib/kernel/qmetaobject_p.h b/src/corelib/kernel/qmetaobject_p.h index bfda30fda28..7264d2a956f 100644 --- a/src/corelib/kernel/qmetaobject_p.h +++ b/src/corelib/kernel/qmetaobject_p.h @@ -124,6 +124,7 @@ struct QMetaObjectPrivate int constructorCount, constructorData; int flags; int signalCount; + int metaObjectHashIndex; static inline const QMetaObjectPrivate *get(const QMetaObject *metaobject) { return reinterpret_cast<const QMetaObjectPrivate*>(metaobject->d.data); } diff --git a/src/corelib/kernel/qmetaobjectbuilder.cpp b/src/corelib/kernel/qmetaobjectbuilder.cpp index 6065bf2baea..9af6de73680 100644 --- a/src/corelib/kernel/qmetaobjectbuilder.cpp +++ b/src/corelib/kernel/qmetaobjectbuilder.cpp @@ -558,6 +558,8 @@ QMetaPropertyBuilder QMetaObjectBuilder::addProperty(const QMetaProperty &protot property.setEnumOrFlag(prototype.isEnumType()); property.setConstant(prototype.isConstant()); property.setFinal(prototype.isFinal()); + property.setVirtual(prototype.isVirtual()); + property.setOverride(prototype.isOverride()); property.setRevision(prototype.revision()); if (prototype.hasNotifySignal()) { // Find an existing method for the notify signal, or add a new one. @@ -1177,10 +1179,11 @@ static int buildMetaObject(QMetaObjectBuilderPrivate *d, char *buf, int methodParametersDataSize = aggregateParameterCount(d->methods) + aggregateParameterCount(d->constructors); if constexpr (mode == Construct) { - static_assert(QMetaObjectPrivate::OutputRevision == 13, "QMetaObjectBuilder should generate the same version as moc"); + static_assert(QMetaObjectPrivate::OutputRevision == 14, "QMetaObjectBuilder should generate the same version as moc"); pmeta->revision = QMetaObjectPrivate::OutputRevision; pmeta->flags = d->flags.toInt() | AllocatedMetaObject; pmeta->className = 0; // Class name is always the first string. + pmeta->metaObjectHashIndex = -1; // TODO support hash in the builder too //pmeta->signalCount is handled in the "output method loop" as an optimization. pmeta->classInfoCount = d->classInfoNames.size(); @@ -2068,6 +2071,32 @@ bool QMetaPropertyBuilder::isFinal() const } /*! + Returns \c true if the property is virtual; otherwise returns \c false. + The default value is false. +*/ +bool QMetaPropertyBuilder::isVirtual() const +{ + QMetaPropertyBuilderPrivate *d = d_func(); + if (d) + return d->flag(Virtual); + else + return false; +} + +/*! + Returns \c true if the property does override; otherwise returns \c false. + The default value is false. +*/ +bool QMetaPropertyBuilder::isOverride() const +{ + QMetaPropertyBuilderPrivate *d = d_func(); + if (d) + return d->flag(Override); + else + return false; +} + +/*! * Returns \c true if the property is an alias. * The default value is false */ @@ -2239,6 +2268,30 @@ void QMetaPropertyBuilder::setFinal(bool value) } /*! + Sets the \c VIRTUAL flag on this property to \a value. + + \sa isFinal() +*/ +void QMetaPropertyBuilder::setVirtual(bool value) +{ + QMetaPropertyBuilderPrivate *d = d_func(); + if (d) + d->setFlag(Virtual, value); +} + +/*! + Sets the \c OVERRIDE flag on this property to \a value. + + \sa isOverride() +*/ +void QMetaPropertyBuilder::setOverride(bool value) +{ + QMetaPropertyBuilderPrivate *d = d_func(); + if (d) + d->setFlag(Override, value); +} + +/*! Sets the \c ALIAS flag on this property to \a value */ void QMetaPropertyBuilder::setAlias(bool value) diff --git a/src/corelib/kernel/qmetaobjectbuilder_p.h b/src/corelib/kernel/qmetaobjectbuilder_p.h index 563704d60e6..9591944602a 100644 --- a/src/corelib/kernel/qmetaobjectbuilder_p.h +++ b/src/corelib/kernel/qmetaobjectbuilder_p.h @@ -214,6 +214,8 @@ public: bool isEnumOrFlag() const; bool isConstant() const; bool isFinal() const; + bool isVirtual() const; + bool isOverride() const; bool isAlias() const; bool isBindable() const; bool isRequired() const; @@ -229,6 +231,8 @@ public: void setEnumOrFlag(bool value); void setConstant(bool value); void setFinal(bool value); + void setVirtual(bool value); + void setOverride(bool value); void setAlias(bool value); void setBindable(bool value); void setRequired(bool value); diff --git a/src/corelib/kernel/qmetasequence.cpp b/src/corelib/kernel/qmetasequence.cpp index 1d3f3dfd080..2a3a923d5ca 100644 --- a/src/corelib/kernel/qmetasequence.cpp +++ b/src/corelib/kernel/qmetasequence.cpp @@ -1,8 +1,8 @@ // Copyright (C) 2025 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -#include "qmetacontainer.h" -#include "qmetatype.h" +#include <QtCore/qmetasequence.h> +#include <QtCore/qmetatype.h> QT_BEGIN_NAMESPACE @@ -468,4 +468,176 @@ void QMetaSequence::valueAtConstIterator(const void *iterator, void *result) con type than the QMetaSequence \a rhs, otherwise returns \c false. */ +/*! + \class QMetaSequence::Iterable + \inherits QIterable + \since 6.11 + \inmodule QtCore + \brief The QMetaSequence::Iterable class is an iterable interface for a container in a QVariant. + + This class allows several methods of accessing the values of a container + held within a QVariant. An instance of QMetaSequence::Iterable can be + extracted from a QVariant if it can be converted to a QVariantList, or if + the container it contains is registered using + Q_DECLARE_SEQUENTIAL_CONTAINER_METATYPE. Most sequential containers found + in Qt and some found in the C++ standard library are automatically + registered. + + \snippet code/src_corelib_kernel_qvariant.cpp 9 + + The container itself is not copied before iterating over it. + + \sa QVariant +*/ + +/*! + \typealias QMetaSequence::Iterable::RandomAccessIterator + Exposes an iterator using std::random_access_iterator_tag. +*/ + +/*! + \typealias QMetaSequence::Iterable::BidirectionalIterator + Exposes an iterator using std::bidirectional_iterator_tag. +*/ + +/*! + \typealias QMetaSequence::Iterable::ForwardIterator + Exposes an iterator using std::forward_iterator_tag. +*/ + +/*! + \typealias QMetaSequence::Iterable::InputIterator + Exposes an iterator using std::input_iterator_tag. +*/ + +/*! + \typealias QMetaSequence::Iterable::RandomAccessConstIterator + Exposes a const_iterator using std::random_access_iterator_tag. +*/ + +/*! + \typealias QMetaSequence::Iterable::BidirectionalConstIterator + Exposes a const_iterator using std::bidirectional_iterator_tag. +*/ + +/*! + \typealias QMetaSequence::Iterable::ForwardConstIterator + Exposes a const_iterator using std::forward_iterator_tag. +*/ + +/*! + \typealias QMetaSequence::Iterable::InputConstIterator + Exposes a const_iterator using std::input_iterator_tag. +*/ + +/*! + \enum QMetaSequence::Iterable::Position + \deprecated [6.11] Use append(), prepend(), removeFirst(), or removeLast() + + Specifies the position at which an element shall be added to or removed from + the iterable. + + \value AtBegin + Add or remove at the beginning of the iterable. + \value AtEnd + Add or remove at the end of the iterable. + \value Unspecified + Add or remove at an unspecified position in the iterable. + */ + +/*! + \fn void QMetaSequence::Iterable::addValue(const QVariant &value, Position position) + \deprecated [6.11] Use append() or prepend() + Adds \a value to the container, at \a position, if possible. + */ + +/*! + \deprecated [6.11] Use removeFirst() or removeLast() + \fn void QMetaSequence::Iterable::removeValue(Position position) + Removes a value from the container, at \a position, if possible. + */ + +/*! + \deprecated [6.11] Use QMetaSequence::valueMetaType() + \fn QMetaType QMetaSequence::Iterable::valueMetaType() const + Returns the meta type for values stored in the underlying container. + */ + +/*! + \fn QVariant QMetaSequence::Iterable::at(qsizetype idx) const + Returns the value at position \a idx in the container. + + \note If the underlying container does not provide a native way to retrieve + an element at an index, this method will synthesize the access using + iterators. This behavior is deprecated and will be removed in a future + version of Qt. +*/ + +/*! + \fn void QMetaSequence::Iterable::set(qsizetype idx, const QVariant &value) + Sets the element at position \a idx in the container to \a value. + + \note If the underlying container does not provide a native way to assign + an element at an index, this method will synthesize the assignment + using iterators. This behavior is deprecated and will be removed in a + future version of Qt. +*/ + +/*! + \class QMetaSequence::Iterable::ConstIterator + \inmodule QtCore + \inherits QConstIterator + \since 6.11 + \brief QMetaSequence::Iterable::ConstIterator allows iteration over a container in a QVariant. + + A QMetaSequence::Iterable::ConstIterator can only be created by a + QMetaSequence::Iterable instance, and can be used in a way similar to other + stl-style iterators. + + \snippet code/src_corelib_kernel_qvariant.cpp 9 +*/ + +/*! + \class QMetaSequence::Iterable::Iterator + \inmodule QtCore + \inherits QIterator + \since 6.11 + \brief QMetaSequence::Iterable::Iterator allows iteration over a container in a QVariant. + + A QMetaSequence::Iterable::Iterator can only be created by a QMetaSequence::Iterable + instance, and can be used in a way similar to other stl-style iterators. +*/ + +/*! + \fn QVariant::Reference<QMetaSequence::Iterable::Iterator> QMetaSequence::Iterable::Iterator::operator*() const + Returns the current item, converted to a QVariant::Reference. +*/ + +/*! + \fn QVariant::Pointer<QMetaSequence::Iterable::Iterator> QMetaSequence::Iterable::Iterator::operator->() const + Returns the current item, converted to a QVariant::Pointer. +*/ + +/*! + \fn QVariant::Reference<QMetaSequence::Iterable::Iterator> QMetaSequence::Iterable::Iterator::operator[](qsizetype n) const + Returns the item offset from the current one by \a n, converted to a + QVariant::Reference. +*/ + +/*! + \fn QVariant QMetaSequence::Iterable::ConstIterator::operator*() const + Returns the current item, converted to a QVariant. +*/ + +/*! + \fn QVariant::ConstPointer<QMetaSequence::Iterable::ConstIterator> QMetaSequence::Iterable::ConstIterator::operator->() const + Returns the current item, converted to a QVariant::ConstPointer. +*/ + +/*! + \fn QVariant QMetaSequence::Iterable::ConstIterator::operator[](qsizetype n) const + Returns the item offset from the current one by \a n, converted to a + QVariant. +*/ + QT_END_NAMESPACE diff --git a/src/corelib/kernel/qmetasequence.h b/src/corelib/kernel/qmetasequence.h new file mode 100644 index 00000000000..26156e7924f --- /dev/null +++ b/src/corelib/kernel/qmetasequence.h @@ -0,0 +1,271 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QMETASEQUENCE_H +#define QMETASEQUENCE_H + +#if 0 +#pragma qt_class(QMetaSequence) +#endif + +#include <QtCore/qiterable.h> +#include <QtCore/qiterable_impl.h> +#include <QtCore/qmetacontainer.h> +#include <QtCore/qvariant.h> + +QT_BEGIN_NAMESPACE + +namespace QtMetaContainerPrivate { + +class SequentialIterator : public QIterator<QMetaSequence> +{ +public: + using value_type = QVariant; + using reference = QVariant::Reference<SequentialIterator>; + using pointer = QVariant::Pointer<SequentialIterator>; + + static constexpr bool CanNoexceptAssignQVariant = false; + static constexpr bool CanNoexceptConvertToQVariant = false; + + SequentialIterator(QIterator &&it) : QIterator(std::move(it)) {} + + reference operator*() const { return reference(*this); } + pointer operator->() const { return pointer(*this); } + reference operator[](qsizetype n) const { return reference(*this + n); } +}; + +class SequentialConstIterator : public QConstIterator<QMetaSequence> +{ +public: + using value_type = QVariant; + using reference = QVariant::ConstReference<SequentialConstIterator>; + using pointer = QVariant::ConstPointer<SequentialConstIterator>; + + static constexpr bool CanNoexceptConvertToQVariant = false; + + SequentialConstIterator(QConstIterator &&it) : QConstIterator(std::move(it)) {} + + value_type operator*() const; + pointer operator->() const { return pointer(*this); } + value_type operator[](qsizetype n) const; +}; + +} // namespace QtMetaContainerPrivate + +namespace QtPrivate { +template<typename Referred> +QVariant sequentialIteratorToVariant(const Referred &referred) +{ + const auto metaSequence = referred.metaContainer(); + return QIterablePrivate::retrieveElement(metaSequence.valueMetaType(), [&](void *dataPtr) { + metaSequence.valueAtConstIterator(referred.constIterator(), dataPtr); + }); +} +} // namespace QtPrivate + +template<> +inline QVariant::Reference<QtMetaContainerPrivate::SequentialIterator>::operator QVariant() const +{ + return QtPrivate::sequentialIteratorToVariant(m_referred); +} + +template<> +inline QVariant::Reference<QtMetaContainerPrivate::SequentialIterator> & +QVariant::Reference<QtMetaContainerPrivate::SequentialIterator>::operator=(const QVariant &value) +{ + QtPrivate::QVariantTypeCoercer coercer; + m_referred.metaContainer().setValueAtIterator( + m_referred.mutableIterator(), + coercer.coerce(value, m_referred.metaContainer().valueMetaType())); + return *this; +} + +template<> +inline QVariant::ConstReference<QtMetaContainerPrivate::SequentialConstIterator>::operator QVariant() const +{ + return QtPrivate::sequentialIteratorToVariant(m_referred); +} + +namespace QtMetaContainerPrivate { +inline SequentialConstIterator::value_type SequentialConstIterator::operator*() const +{ + return reference(*this); +} + +inline SequentialConstIterator::value_type SequentialConstIterator::operator[](qsizetype n) const +{ + return reference(*this + n); +} + +class Sequence : public QIterable<QMetaSequence> +{ +public: + using Iterator = QTaggedIterator<SequentialIterator, void>; + using RandomAccessIterator + = QTaggedIterator<SequentialIterator, std::random_access_iterator_tag>; + using BidirectionalIterator + = QTaggedIterator<SequentialIterator, std::bidirectional_iterator_tag>; + using ForwardIterator + = QTaggedIterator<SequentialIterator, std::forward_iterator_tag>; + using InputIterator + = QTaggedIterator<SequentialIterator, std::input_iterator_tag>; + + using ConstIterator + = QTaggedIterator<SequentialConstIterator, void>; + using RandomAccessConstIterator + = QTaggedIterator<SequentialConstIterator, std::random_access_iterator_tag>; + using BidirectionalConstIterator + = QTaggedIterator<SequentialConstIterator, std::bidirectional_iterator_tag>; + using ForwardConstIterator + = QTaggedIterator<SequentialConstIterator, std::forward_iterator_tag>; + using InputConstIterator + = QTaggedIterator<SequentialConstIterator, std::input_iterator_tag>; + + using iterator = Iterator; + using const_iterator = ConstIterator; + + template<class T> + Sequence(const T *p) + : QIterable(QMetaSequence::fromContainer<T>(), p) + { + Q_UNUSED(m_revision); + } + + template<class T> + Sequence(T *p) + : QIterable(QMetaSequence::fromContainer<T>(), p) + { + } + + Sequence() + : QIterable(QMetaSequence(), nullptr) + { + } + + template<typename Pointer> + Sequence(const QMetaSequence &metaSequence, Pointer iterable) + : QIterable(metaSequence, iterable) + { + } + + Sequence(const QMetaSequence &metaSequence, QMetaType metaType, void *iterable) + : QIterable(metaSequence, metaType.alignOf(), iterable) + { + } + + Sequence(const QMetaSequence &metaSequence, QMetaType metaType, const void *iterable) + : QIterable(metaSequence, metaType.alignOf(), iterable) + { + } + + Sequence(QIterable<QMetaSequence> &&other) : QIterable(std::move(other)) {} + + Sequence &operator=(QIterable<QMetaSequence> &&other) + { + QIterable::operator=(std::move(other)); + return *this; + } + + ConstIterator begin() const { return constBegin(); } + ConstIterator end() const { return constEnd(); } + + ConstIterator constBegin() const { return ConstIterator(QIterable::constBegin()); } + ConstIterator constEnd() const { return ConstIterator(QIterable::constEnd()); } + + Iterator mutableBegin() { return Iterator(QIterable::mutableBegin()); } + Iterator mutableEnd() { return Iterator(QIterable::mutableEnd()); } + + QVariant at(qsizetype idx) const + { + const QMetaSequence meta = metaContainer(); + return QIterablePrivate::retrieveElement(meta.valueMetaType(), [&](void *dataPtr) { + if (meta.canGetValueAtIndex()) { + meta.valueAtIndex(constIterable(), idx, dataPtr); + return; + } + +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) + // We shouldn't second-guess the underlying container. + QtPrivate::warnSynthesizedAccess( + "at() called on an iterable without native indexed accessors. This is slow"); + void *it = meta.constBegin(m_iterable.constPointer()); + meta.advanceConstIterator(it, idx); + meta.valueAtConstIterator(it, dataPtr); + meta.destroyConstIterator(it); +#endif + }); + } + + void set(qsizetype idx, const QVariant &value) + { + const QMetaSequence meta = metaContainer(); + QtPrivate::QVariantTypeCoercer coercer; + const void *dataPtr = coercer.coerce(value, meta.valueMetaType()); + if (meta.canSetValueAtIndex()) { + meta.setValueAtIndex(mutableIterable(), idx, dataPtr); + return; + } + +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) + // We shouldn't second-guess the underlying container + QtPrivate::warnSynthesizedAccess( + "set() called on an iterable without native indexed accessors. This is slow"); + void *it = meta.begin(m_iterable.mutablePointer()); + meta.advanceIterator(it, idx); + meta.setValueAtIterator(it, dataPtr); + meta.destroyIterator(it); +#endif + } + + void append(const QVariant &value) + { + const QMetaSequence meta = metaContainer(); + QtPrivate::QVariantTypeCoercer coercer; + meta.addValueAtEnd(mutableIterable(), coercer.coerce(value, meta.valueMetaType())); + } + + void prepend(const QVariant &value) + { + const QMetaSequence meta = metaContainer(); + QtPrivate::QVariantTypeCoercer coercer; + meta.addValueAtBegin(mutableIterable(), coercer.coerce(value, meta.valueMetaType())); + } + + void removeLast() + { + metaContainer().removeValueAtEnd(mutableIterable()); + } + + void removeFirst() + { + metaContainer().removeValueAtBegin(mutableIterable()); + } + +#if QT_DEPRECATED_SINCE(6, 11) + QT_WARNING_PUSH + QT_WARNING_DISABLE_DEPRECATED + + enum + QT_DEPRECATED_VERSION_X_6_11("Use append(), prepend(), removeLast(), or removeFirst() instead.") + Position: quint8 + { + Unspecified, AtBegin, AtEnd + }; + + void addValue(const QVariant &value, Position position = Unspecified) + Q_DECL_EQ_DELETE_X("Use append() or prepend() instead."); + + void removeValue(Position position = Unspecified) + Q_DECL_EQ_DELETE_X("Use removeLast() or removeFirst() instead."); + + QMetaType valueMetaType() const + Q_DECL_EQ_DELETE_X("Use QMetaSequence::valueMetaType() instead."); + + QT_WARNING_POP +#endif // QT_DEPRECATED_SINCE(6, 11) +}; +} // namespace QtMetaContainerPrivate + +QT_END_NAMESPACE + +#endif // QMETASEQUENCE_H diff --git a/src/corelib/kernel/qmetatype.cpp b/src/corelib/kernel/qmetatype.cpp index 1850a148d19..565f9182e68 100644 --- a/src/corelib/kernel/qmetatype.cpp +++ b/src/corelib/kernel/qmetatype.cpp @@ -43,7 +43,9 @@ # include "qjsonvalue.h" # include "qline.h" # include "qloggingcategory.h" +# include "qmetaassociation.h" # include "qmetaobject.h" +# include "qmetasequence.h" # include "qobject.h" # include "qpoint.h" # include "qrect.h" @@ -2151,25 +2153,28 @@ static bool convertToEnum(QMetaType fromType, const void *from, QMetaType toType } } -static bool convertIterableToVariantList(QMetaType fromType, const void *from, void *to) +template<typename Iterable> +bool convertIterableToVariantList(QMetaType fromType, const void *from, void *to) { - QSequentialIterable list; - if (!QMetaType::convert(fromType, from, QMetaType::fromType<QSequentialIterable>(), &list)) + Iterable list; + if (!QMetaType::convert(fromType, from, QMetaType::fromType<Iterable>(), &list)) return false; QVariantList &l = *static_cast<QVariantList *>(to); l.clear(); - l.reserve(list.size()); + if (list.metaContainer().hasSize()) + l.reserve(list.size()); auto end = list.end(); for (auto it = list.begin(); it != end; ++it) l << *it; return true; } -static bool convertIterableToVariantMap(QMetaType fromType, const void *from, void *to) +template<typename Iterable> +bool convertIterableToVariantMap(QMetaType fromType, const void *from, void *to) { - QAssociativeIterable map; - if (!QMetaType::convert(fromType, from, QMetaType::fromType<QAssociativeIterable>(), &map)) + Iterable map; + if (!QMetaType::convert(fromType, from, QMetaType::fromType<Iterable>(), &map)) return false; QVariantMap &h = *static_cast<QVariantMap *>(to); @@ -2180,10 +2185,11 @@ static bool convertIterableToVariantMap(QMetaType fromType, const void *from, vo return true; } -static bool convertIterableToVariantHash(QMetaType fromType, const void *from, void *to) +template<typename Iterable> +bool convertIterableToVariantHash(QMetaType fromType, const void *from, void *to) { - QAssociativeIterable map; - if (!QMetaType::convert(fromType, from, QMetaType::fromType<QAssociativeIterable>(), &map)) + Iterable map; + if (!QMetaType::convert(fromType, from, QMetaType::fromType<Iterable>(), &map)) return false; QVariantHash &h = *static_cast<QVariantHash *>(to); @@ -2225,33 +2231,34 @@ static bool convertIterableToVariantPair(QMetaType fromType, const void *from, v return true; } +template<typename Iterable> static bool convertToSequentialIterable(QMetaType fromType, const void *from, void *to) { using namespace QtMetaTypePrivate; const int fromTypeId = fromType.id(); - QSequentialIterable &i = *static_cast<QSequentialIterable *>(to); + Iterable &i = *static_cast<Iterable *>(to); switch (fromTypeId) { case QMetaType::QVariantList: - i = QSequentialIterable(reinterpret_cast<const QVariantList *>(from)); + i = Iterable(reinterpret_cast<const QVariantList *>(from)); return true; case QMetaType::QStringList: - i = QSequentialIterable(reinterpret_cast<const QStringList *>(from)); + i = Iterable(reinterpret_cast<const QStringList *>(from)); return true; case QMetaType::QByteArrayList: - i = QSequentialIterable(reinterpret_cast<const QByteArrayList *>(from)); + i = Iterable(reinterpret_cast<const QByteArrayList *>(from)); return true; case QMetaType::QString: - i = QSequentialIterable(reinterpret_cast<const QString *>(from)); + i = Iterable(reinterpret_cast<const QString *>(from)); return true; case QMetaType::QByteArray: - i = QSequentialIterable(reinterpret_cast<const QByteArray *>(from)); + i = Iterable(reinterpret_cast<const QByteArray *>(from)); return true; default: { - QSequentialIterable impl; + QIterable<QMetaSequence> j(QMetaSequence(), nullptr); if (QMetaType::convert( - fromType, from, QMetaType::fromType<QIterable<QMetaSequence>>(), &impl)) { - i = std::move(impl); + fromType, from, QMetaType::fromType<QIterable<QMetaSequence>>(), &j)) { + i = std::move(j); return true; } } @@ -2289,27 +2296,28 @@ static bool canImplicitlyViewAsSequentialIterable(QMetaType fromType) } } +template<typename Iterable> static bool viewAsSequentialIterable(QMetaType fromType, void *from, void *to) { using namespace QtMetaTypePrivate; const int fromTypeId = fromType.id(); - QSequentialIterable &i = *static_cast<QSequentialIterable *>(to); + Iterable &i = *static_cast<Iterable *>(to); switch (fromTypeId) { case QMetaType::QVariantList: - i = QSequentialIterable(reinterpret_cast<QVariantList *>(from)); + i = Iterable(reinterpret_cast<QVariantList *>(from)); return true; case QMetaType::QStringList: - i = QSequentialIterable(reinterpret_cast<QStringList *>(from)); + i = Iterable(reinterpret_cast<QStringList *>(from)); return true; case QMetaType::QByteArrayList: - i = QSequentialIterable(reinterpret_cast<QByteArrayList *>(from)); + i = Iterable(reinterpret_cast<QByteArrayList *>(from)); return true; case QMetaType::QString: - i = QSequentialIterable(reinterpret_cast<QString *>(from)); + i = Iterable(reinterpret_cast<QString *>(from)); return true; case QMetaType::QByteArray: - i = QSequentialIterable(reinterpret_cast<QByteArray *>(from)); + i = Iterable(reinterpret_cast<QByteArray *>(from)); return true; default: { QIterable<QMetaSequence> j(QMetaSequence(), nullptr); @@ -2324,24 +2332,25 @@ static bool viewAsSequentialIterable(QMetaType fromType, void *from, void *to) return false; } +template<typename Iterable> static bool convertToAssociativeIterable(QMetaType fromType, const void *from, void *to) { using namespace QtMetaTypePrivate; - QAssociativeIterable &i = *static_cast<QAssociativeIterable *>(to); + Iterable &i = *static_cast<Iterable *>(to); if (fromType.id() == QMetaType::QVariantMap) { - i = QAssociativeIterable(reinterpret_cast<const QVariantMap *>(from)); + i = Iterable(reinterpret_cast<const QVariantMap *>(from)); return true; } if (fromType.id() == QMetaType::QVariantHash) { - i = QAssociativeIterable(reinterpret_cast<const QVariantHash *>(from)); + i = Iterable(reinterpret_cast<const QVariantHash *>(from)); return true; } - QAssociativeIterable impl; + QIterable<QMetaAssociation> j(QMetaAssociation(), nullptr); if (QMetaType::convert( - fromType, from, QMetaType::fromType<QIterable<QMetaAssociation>>(), &impl)) { - i = std::move(impl); + fromType, from, QMetaType::fromType<QIterable<QMetaAssociation>>(), &j)) { + i = std::move(j); return true; } @@ -2384,18 +2393,19 @@ static bool canImplicitlyViewAsAssociativeIterable(QMetaType fromType) } } +template<typename Iterable> static bool viewAsAssociativeIterable(QMetaType fromType, void *from, void *to) { using namespace QtMetaTypePrivate; int fromTypeId = fromType.id(); - QAssociativeIterable &i = *static_cast<QAssociativeIterable *>(to); + Iterable &i = *static_cast<Iterable *>(to); if (fromTypeId == QMetaType::QVariantMap) { - i = QAssociativeIterable(reinterpret_cast<QVariantMap *>(from)); + i = Iterable(reinterpret_cast<QVariantMap *>(from)); return true; } if (fromTypeId == QMetaType::QVariantHash) { - i = QAssociativeIterable(reinterpret_cast<QVariantHash *>(from)); + i = Iterable(reinterpret_cast<QVariantHash *>(from)); return true; } @@ -2493,20 +2503,54 @@ bool QMetaType::convert(QMetaType fromType, const void *from, QMetaType toType, return true; // handle iterables - if (toTypeId == QVariantList && convertIterableToVariantList(fromType, from, to)) + if (toTypeId == QVariantList + && convertIterableToVariantList<QMetaSequence::Iterable>(fromType, from, to)) { return true; + } - if (toTypeId == QVariantMap && convertIterableToVariantMap(fromType, from, to)) + if (toTypeId == QVariantMap + && convertIterableToVariantMap<QMetaAssociation::Iterable>(fromType, from, to)) { return true; + } - if (toTypeId == QVariantHash && convertIterableToVariantHash(fromType, from, to)) + if (toTypeId == QVariantHash + && convertIterableToVariantHash<QMetaAssociation::Iterable>(fromType, from, to)) { return true; + } + + if (toTypeId == qMetaTypeId<QMetaSequence::Iterable>()) + return convertToSequentialIterable<QMetaSequence::Iterable>(fromType, from, to); + + if (toTypeId == qMetaTypeId<QMetaAssociation::Iterable>()) + return convertToAssociativeIterable<QMetaAssociation::Iterable>(fromType, from, to); + +#if QT_DEPRECATED_SINCE(6, 13) + QT_WARNING_PUSH + QT_WARNING_DISABLE_DEPRECATED + + if (toTypeId == QVariantList + && convertIterableToVariantList<QSequentialIterable>(fromType, from, to)) { + return true; + } + + if (toTypeId == QVariantMap + && convertIterableToVariantMap<QAssociativeIterable>(fromType, from, to)) { + return true; + } + + if (toTypeId == QVariantHash + && convertIterableToVariantHash<QAssociativeIterable>(fromType, from, to)) { + return true; + } if (toTypeId == qMetaTypeId<QSequentialIterable>()) - return convertToSequentialIterable(fromType, from, to); + return convertToSequentialIterable<QSequentialIterable>(fromType, from, to); if (toTypeId == qMetaTypeId<QAssociativeIterable>()) - return convertToAssociativeIterable(fromType, from, to); + return convertToAssociativeIterable<QAssociativeIterable>(fromType, from, to); + + QT_WARNING_POP +#endif // QT_DEPRECATED_SINCE(6, 13) return convertMetaObject(fromType, from, toType, to); } @@ -2528,11 +2572,24 @@ bool QMetaType::view(QMetaType fromType, void *from, QMetaType toType, void *to) if (f) return (*f)(from, to); + if (toTypeId == qMetaTypeId<QMetaSequence::Iterable>()) + return viewAsSequentialIterable<QMetaSequence::Iterable>(fromType, from, to); + + if (toTypeId == qMetaTypeId<QMetaAssociation::Iterable>()) + return viewAsAssociativeIterable<QMetaAssociation::Iterable>(fromType, from, to); + +#if QT_DEPRECATED_SINCE(6, 13) + QT_WARNING_PUSH + QT_WARNING_DISABLE_DEPRECATED + if (toTypeId == qMetaTypeId<QSequentialIterable>()) - return viewAsSequentialIterable(fromType, from, to); + return viewAsSequentialIterable<QSequentialIterable>(fromType, from, to); if (toTypeId == qMetaTypeId<QAssociativeIterable>()) - return viewAsAssociativeIterable(fromType, from, to); + return viewAsAssociativeIterable<QAssociativeIterable>(fromType, from, to); + + QT_WARNING_POP +#endif // QT_DEPRECATED_SINCE(6, 13) return convertMetaObject(fromType, from, toType, to); } @@ -2545,14 +2602,14 @@ bool QMetaType::view(QMetaType fromType, void *from, QMetaType toType, void *to) function if a qobject_cast from the type described by \a fromType to the type described by \a toType would succeed. - You can create a mutable view of type QSequentialIterable on any container registered with + You can create a mutable view of type QMetaSequence::Iterable on any container registered with Q_DECLARE_SEQUENTIAL_CONTAINER_METATYPE(). - Similarly you can create a mutable view of type QAssociativeIterable on any container + Similarly you can create a mutable view of type QMetaAssociation::Iterable on any container registered with Q_DECLARE_ASSOCIATIVE_CONTAINER_METATYPE(). - \sa convert(), QSequentialIterable, Q_DECLARE_SEQUENTIAL_CONTAINER_METATYPE(), - QAssociativeIterable, Q_DECLARE_ASSOCIATIVE_CONTAINER_METATYPE() + \sa convert(), QMetaSequence::Iterable, Q_DECLARE_SEQUENTIAL_CONTAINER_METATYPE(), + QMetaAssociation::Iterable, Q_DECLARE_ASSOCIATIVE_CONTAINER_METATYPE() */ bool QMetaType::canView(QMetaType fromType, QMetaType toType) { @@ -2566,12 +2623,25 @@ bool QMetaType::canView(QMetaType fromType, QMetaType toType) if (f) return true; + if (toTypeId == qMetaTypeId<QMetaSequence::Iterable>()) + return canImplicitlyViewAsSequentialIterable(fromType); + + if (toTypeId == qMetaTypeId<QMetaAssociation::Iterable>()) + return canImplicitlyViewAsAssociativeIterable(fromType); + +#if QT_DEPRECATED_SINCE(6, 13) + QT_WARNING_PUSH + QT_WARNING_DISABLE_DEPRECATED + if (toTypeId == qMetaTypeId<QSequentialIterable>()) return canImplicitlyViewAsSequentialIterable(fromType); if (toTypeId == qMetaTypeId<QAssociativeIterable>()) return canImplicitlyViewAsAssociativeIterable(fromType); + QT_WARNING_POP +#endif + if (canConvertMetaObject(fromType, toType)) return true; @@ -2660,8 +2730,8 @@ bool QMetaType::canView(QMetaType fromType, QMetaType toType) Similarly, a cast from an associative container will also return true for this function the \a toType is QVariantHash or QVariantMap. - \sa convert(), QSequentialIterable, Q_DECLARE_SEQUENTIAL_CONTAINER_METATYPE(), QAssociativeIterable, - Q_DECLARE_ASSOCIATIVE_CONTAINER_METATYPE() + \sa convert(), QMetaSequence::Iterable, Q_DECLARE_SEQUENTIAL_CONTAINER_METATYPE(), + QMetaAssociation::Iterable, Q_DECLARE_ASSOCIATIVE_CONTAINER_METATYPE() */ bool QMetaType::canConvert(QMetaType fromType, QMetaType toType) { @@ -2682,11 +2752,32 @@ bool QMetaType::canConvert(QMetaType fromType, QMetaType toType) if (f) return true; + if (toTypeId == qMetaTypeId<QMetaSequence::Iterable>()) + return canConvertToSequentialIterable(fromType); + + if (toTypeId == qMetaTypeId<QMetaAssociation::Iterable>()) + return canConvertToAssociativeIterable(fromType); + + if (toTypeId == QVariantList + && canConvert(fromType, QMetaType::fromType<QMetaSequence::Iterable>())) { + return true; + } + + if ((toTypeId == QVariantHash || toTypeId == QVariantMap) + && canConvert(fromType, QMetaType::fromType<QMetaAssociation::Iterable>())) { + return true; + } + +#if QT_DEPRECATED_SINCE(6, 13) + QT_WARNING_PUSH + QT_WARNING_DISABLE_DEPRECATED + if (toTypeId == qMetaTypeId<QSequentialIterable>()) return canConvertToSequentialIterable(fromType); if (toTypeId == qMetaTypeId<QAssociativeIterable>()) return canConvertToAssociativeIterable(fromType); + if (toTypeId == QVariantList && canConvert(fromType, QMetaType::fromType<QSequentialIterable>())) { return true; @@ -2697,6 +2788,9 @@ bool QMetaType::canConvert(QMetaType fromType, QMetaType toType) return true; } + QT_WARNING_POP +#endif // QT_DEPRECATED_SINCE(6, 13) + if (toTypeId == QVariantPair && hasRegisteredConverterFunction( fromType, QMetaType::fromType<QtMetaTypePrivate::QPairVariantInterfaceImpl>())) return true; diff --git a/src/corelib/kernel/qobject.cpp b/src/corelib/kernel/qobject.cpp index 02c9f00f301..607dc23f56c 100644 --- a/src/corelib/kernel/qobject.cpp +++ b/src/corelib/kernel/qobject.cpp @@ -2696,23 +2696,38 @@ static void err_method_notfound(const QObject *object, case QSIGNAL_CODE: type = "signal"; break; } const char *loc = extract_location(method); + const char *err; if (strchr(method, ')') == nullptr) // common typing mistake - qCWarning(lcConnect, "QObject::%s: Parentheses expected, %s %s::%s%s%s", func, type, - object->metaObject()->className(), method + 1, loc ? " in " : "", loc ? loc : ""); + err = "Parentheses expected,"; else - qCWarning(lcConnect, "QObject::%s: No such %s %s::%s%s%s", func, type, - object->metaObject()->className(), method + 1, loc ? " in " : "", loc ? loc : ""); + err = "No such"; + qCWarning(lcConnect, "QObject::%s: %s %s %s::%s%s%s", func, err, type, + object->metaObject()->className(), method + 1, loc ? " in " : "", loc ? loc : ""); +} + +enum class ConnectionEnd : bool { Sender, Receiver }; +Q_DECL_COLD_FUNCTION +static void err_info_about_object(const char *func, const QObject *o, ConnectionEnd end) +{ + if (!o) + return; + const QString name = o->objectName(); + if (name.isEmpty()) + return; + const bool sender = end == ConnectionEnd::Sender; + qCWarning(lcConnect, "QObject::%s: (%s name:%*s'%ls')", + func, + sender ? "sender" : "receiver", + sender ? 3 : 1, // ← length of generated whitespace + "", + qUtf16Printable(name)); } Q_DECL_COLD_FUNCTION static void err_info_about_objects(const char *func, const QObject *sender, const QObject *receiver) { - QString a = sender ? sender->objectName() : QString(); - QString b = receiver ? receiver->objectName() : QString(); - if (!a.isEmpty()) - qCWarning(lcConnect, "QObject::%s: (sender name: '%s')", func, a.toLocal8Bit().data()); - if (!b.isEmpty()) - qCWarning(lcConnect, "QObject::%s: (receiver name: '%s')", func, b.toLocal8Bit().data()); + err_info_about_object(func, sender, ConnectionEnd::Sender); + err_info_about_object(func, receiver, ConnectionEnd::Receiver); } Q_DECL_COLD_FUNCTION diff --git a/src/corelib/kernel/qobjectdefs.h b/src/corelib/kernel/qobjectdefs.h index 848102cc57a..d3e761982f5 100644 --- a/src/corelib/kernel/qobjectdefs.h +++ b/src/corelib/kernel/qobjectdefs.h @@ -247,6 +247,8 @@ struct Q_CORE_EXPORT QMetaObject QMetaType metaType() const; + const char *metaObjectHash() const; + int methodOffset() const; int enumeratorOffset() const; int propertyOffset() const; diff --git a/src/corelib/kernel/qpermissions.cpp b/src/corelib/kernel/qpermissions.cpp index bbcea8338ca..69383dafdd9 100644 --- a/src/corelib/kernel/qpermissions.cpp +++ b/src/corelib/kernel/qpermissions.cpp @@ -119,12 +119,16 @@ Q_LOGGING_CATEGORY(lcPermissions, "qt.permissions", QtWarningMsg); To ensure the relevant permission backend is included with your application, please \l {QT_ANDROID_PACKAGE_SOURCE_DIR} {point the build system to your custom \c AndroidManifest.xml} - or use \l {qt_add_android_permission()}. + or use \l {qt_add_android_permission}(). The relevant permission names are described in the documentation for each permission type. - \sa {Qt Creator: Editing Manifest Files} + \note When using this API, the \c{<!-- %%INSERT_PERMISSIONS -->} tag must be present in + the AndroidManifest.xml. For further information on the use of this tag, + see \l {Qt Permissions and Features} + + \sa {Qt Creator: Editing Manifest Files}. \section1 Available Permissions diff --git a/src/corelib/kernel/qproperty.cpp b/src/corelib/kernel/qproperty.cpp index d538ed7b4e9..9141a8f8bad 100644 --- a/src/corelib/kernel/qproperty.cpp +++ b/src/corelib/kernel/qproperty.cpp @@ -402,6 +402,16 @@ QPropertyBindingPrivate::NotificationState QPropertyBindingPrivate::notifyNonRec } /*! + \class QUntypedPropertyBinding + \inmodule QtCore + \since 6.0 + \ingroup tools + \brief Represents a type-erased property binding. + + \sa QUntypedBindable +*/ + +/*! Constructs a null QUntypedPropertyBinding. \sa isNull() @@ -409,8 +419,8 @@ QPropertyBindingPrivate::NotificationState QPropertyBindingPrivate::notifyNonRec QUntypedPropertyBinding::QUntypedPropertyBinding() = default; /*! - \fn template<typename Functor> - QUntypedPropertyBinding(QMetaType metaType, Functor &&f, const QPropertyBindingSourceLocation &location) + \fn template<typename Functor> QUntypedPropertyBinding( + QMetaType metaType, Functor &&f, const QPropertyBindingSourceLocation &location) \internal */ @@ -448,7 +458,6 @@ QUntypedPropertyBinding::QUntypedPropertyBinding(const QUntypedPropertyBinding & : d(other.d) { } - /*! Copy-assigns \a other to this QUntypedPropertyBinding. */ @@ -1183,7 +1192,7 @@ QString QPropertyBindingError::description() const \return \c true when the binding was successfully set. - //! \sa QUntypedPropertyBinding::valueMetaType() + \sa QUntypedPropertyBinding::valueMetaType() */ /*! @@ -1199,8 +1208,7 @@ QString QPropertyBindingError::description() const Returns the metatype of the property from which the QUntypedBindable was created. If the bindable is invalid, an invalid metatype will be returned. - \sa isValid() - //! \sa QUntypedPropertyBinding::valueMetaType() + \sa isValid(), QUntypedPropertyBinding::valueMetaType() */ /*! diff --git a/src/corelib/kernel/qsequentialiterable.cpp b/src/corelib/kernel/qsequentialiterable.cpp index 32c58266045..b256b129d2c 100644 --- a/src/corelib/kernel/qsequentialiterable.cpp +++ b/src/corelib/kernel/qsequentialiterable.cpp @@ -7,8 +7,13 @@ QT_BEGIN_NAMESPACE +#if QT_DEPRECATED_SINCE(6, 15) +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED + /*! \class QSequentialIterable + \deprecated [6.15] Use QMetaSequence::Iterable instead. \since 5.2 \inmodule QtCore \brief The QSequentialIterable class is an iterable interface for a container in a QVariant. @@ -17,8 +22,6 @@ QT_BEGIN_NAMESPACE a QVariant. An instance of QSequentialIterable can be extracted from a QVariant if it can be converted to a QVariantList. - \snippet code/src_corelib_kernel_qvariant.cpp 9 - The container itself is not copied before iterating over it. \sa QVariant @@ -160,17 +163,17 @@ void QSequentialIterable::set(qsizetype idx, const QVariant &value) /*! \typealias QSequentialIterable::const_iterator + \deprecated [6.15] Use QMetaSequence::Iterable::ConstIterator instead. \brief The QSequentialIterable::const_iterator allows iteration over a container in a QVariant. A QSequentialIterable::const_iterator can only be created by a QSequentialIterable instance, and can be used in a way similar to other stl-style iterators. - - \snippet code/src_corelib_kernel_qvariant.cpp 9 */ /*! \typealias QSequentialIterable::iterator \since 6.0 + \deprecated [6.15] Use QMetaSequence::Iterable::Iterator instead. \brief The QSequentialIterable::iterator allows iteration over a container in a QVariant. A QSequentialIterable::iterator can only be created by a QSequentialIterable instance, @@ -221,4 +224,7 @@ QVariantConstPointer QSequentialConstIterator::operator->() const return QVariantConstPointer(operator*()); } +QT_WARNING_POP +#endif // QT_DEPRECATED_SINCE(6, 15) + QT_END_NAMESPACE diff --git a/src/corelib/kernel/qsequentialiterable.h b/src/corelib/kernel/qsequentialiterable.h index dac146d2ad3..76908bdae4b 100644 --- a/src/corelib/kernel/qsequentialiterable.h +++ b/src/corelib/kernel/qsequentialiterable.h @@ -9,7 +9,21 @@ QT_BEGIN_NAMESPACE -class Q_CORE_EXPORT QSequentialIterator : public QIterator<QMetaSequence> +#if QT_DEPRECATED_SINCE(6, 15) +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED + +#if defined(Q_CC_GNU_ONLY) && Q_CC_GNU < 1300 + // GCC < 13 doesn't accept both deprecation and visibility on the same class + #define QT_CORE_DEPRECATED_EXPORT_VERSION_X_6_15(text) Q_CORE_EXPORT +#else + #define QT_CORE_DEPRECATED_EXPORT_VERSION_X_6_15(text) \ + Q_CORE_EXPORT QT_DEPRECATED_VERSION_X_6_15(text) +#endif + +class +QT_CORE_DEPRECATED_EXPORT_VERSION_X_6_15("Use QMetaSequence::Iterable::Iterator instead.") +QSequentialIterator : public QIterator<QMetaSequence> { public: using value_type = QVariant; @@ -24,7 +38,9 @@ public: QVariantPointer<QSequentialIterator> operator->() const; }; -class Q_CORE_EXPORT QSequentialConstIterator : public QConstIterator<QMetaSequence> +class +QT_CORE_DEPRECATED_EXPORT_VERSION_X_6_15("Use QMetaSequence::Iterable::ConstIterator instead.") +QSequentialConstIterator : public QConstIterator<QMetaSequence> { public: using value_type = QVariant; @@ -39,7 +55,9 @@ public: QVariantConstPointer operator->() const; }; -class Q_CORE_EXPORT QSequentialIterable : public QIterable<QMetaSequence> +class +QT_CORE_DEPRECATED_EXPORT_VERSION_X_6_15("Use QMetaSequence::Iterable instead.") +QSequentialIterable : public QIterable<QMetaSequence> { public: using iterator = QTaggedIterator<QSequentialIterator, void>; @@ -79,14 +97,12 @@ public: { } - // ### Qt7: Pass QMetaType as value rather than const ref. QSequentialIterable(const QMetaSequence &metaSequence, const QMetaType &metaType, void *iterable) : QIterable(metaSequence, metaType.alignOf(), iterable) { } - // ### Qt7: Pass QMetaType as value rather than const ref. QSequentialIterable(const QMetaSequence &metaSequence, const QMetaType &metaType, const void *iterable) : QIterable(metaSequence, metaType.alignOf(), iterable) @@ -126,10 +142,13 @@ inline QVariantRef<QSequentialIterator>::operator QVariant() const if (m_pointer == nullptr) return QVariant(); const QMetaType metaType(m_pointer->metaContainer().valueMetaType()); + + return [&] { QVariant v(metaType); void *dataPtr = metaType == QMetaType::fromType<QVariant>() ? &v : v.data(); m_pointer->metaContainer().valueAtIterator(m_pointer->constIterator(), dataPtr); return v; + }(); } template<> @@ -150,6 +169,11 @@ Q_DECLARE_TYPEINFO(QSequentialIterable, Q_RELOCATABLE_TYPE); Q_DECLARE_TYPEINFO(QSequentialIterable::iterator, Q_RELOCATABLE_TYPE); Q_DECLARE_TYPEINFO(QSequentialIterable::const_iterator, Q_RELOCATABLE_TYPE); +#undef QT_CORE_DEPRECATED_EXPORT_VERSION_X_6_15 + +QT_WARNING_POP +#endif // QT_DEPRECATED_SINCE(6, 15) + QT_END_NAMESPACE #endif // QSEQUENTIALITERABLE_H diff --git a/src/corelib/kernel/qtmocconstants.h b/src/corelib/kernel/qtmocconstants.h index 79c0138bb28..822e02e6c8e 100644 --- a/src/corelib/kernel/qtmocconstants.h +++ b/src/corelib/kernel/qtmocconstants.h @@ -30,7 +30,8 @@ namespace QtMocConstants { // revision 11 is Qt 6.5: The metatype for void is stored in the metatypes array // revision 12 is Qt 6.6: It adds the metatype for enums // revision 13 is Qt 6.9: Adds support for 64-bit QFlags and moves the method revision -enum { OutputRevision = 13 }; // Used by moc, qmetaobjectbuilder and qdbus +// revision 14 is Qt 6.11: Adds a hash of meta object contents +enum { OutputRevision = 14 }; // Used by moc, qmetaobjectbuilder and qdbus enum PropertyFlags : uint { Invalid = 0x00000000, @@ -39,7 +40,8 @@ enum PropertyFlags : uint { Resettable = 0x00000004, EnumOrFlag = 0x00000008, Alias = 0x00000010, - // Reserved for future usage = 0x00000020, + Virtual = 0x00000020, + Override = 0x00000040, StdCppSet = 0x00000100, Constant = 0x00000400, Final = 0x00000800, diff --git a/src/corelib/kernel/qtmochelpers.h b/src/corelib/kernel/qtmochelpers.h index 4c549e78ad5..3d2b59d2a73 100644 --- a/src/corelib/kernel/qtmochelpers.h +++ b/src/corelib/kernel/qtmochelpers.h @@ -511,7 +511,8 @@ template <typename ObjectType, typename Unique, typename Strings, typename Constructors = UintData<>, typename ClassInfo = detail::UintDataBlock<0, 0>> constexpr auto metaObjectData(uint flags, const Strings &strings, const Methods &methods, const Properties &properties, - const Enums &enums, const Constructors &constructors = {}, + const Enums &enums, int qt_metaObjectHashIndex = -1, + const Constructors &constructors = {}, const ClassInfo &classInfo = {}) { constexpr uint MetaTypeCount = Properties::metaTypeCount() @@ -520,7 +521,7 @@ constexpr auto metaObjectData(uint flags, const Strings &strings, + Methods::metaTypeCount() + Constructors::metaTypeCount(); - constexpr uint HeaderSize = 14; + constexpr uint HeaderSize = 15; constexpr uint TotalSize = HeaderSize + Properties::dataSize() + Enums::dataSize() @@ -582,6 +583,8 @@ constexpr auto metaObjectData(uint flags, const Strings &strings, } } + data[14] = qt_metaObjectHashIndex; + return result; } diff --git a/src/corelib/kernel/qtranslator.cpp b/src/corelib/kernel/qtranslator.cpp index 9fdac89f775..6000edaa177 100644 --- a/src/corelib/kernel/qtranslator.cpp +++ b/src/corelib/kernel/qtranslator.cpp @@ -392,8 +392,8 @@ public: translation files may contain misleading or malicious translations. \sa QCoreApplication::installTranslator(), QCoreApplication::removeTranslator(), - QObject::tr(), QCoreApplication::translate(), {I18N Example}, - {Hello tr() Example}, {Arrow Pad Example}, {Troll Print Example} + QObject::tr(), QCoreApplication::translate(), + {Localized Clock Example}, {Arrow Pad Example}, {Troll Print Example} */ /*! diff --git a/src/corelib/kernel/qvariant.cpp b/src/corelib/kernel/qvariant.cpp index 57089f164b2..7cad20e9fd4 100644 --- a/src/corelib/kernel/qvariant.cpp +++ b/src/corelib/kernel/qvariant.cpp @@ -2852,9 +2852,14 @@ const void *QtPrivate::QVariantTypeCoercer::coerce(const QVariant &value, const return converted.constData(); } +#if QT_DEPRECATED_SINCE(6, 15) +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED + /*! \class QVariantRef \since 6.0 + \deprecated [6.15] Use QVariant::Reference instead. \inmodule QtCore \brief The QVariantRef acts as a non-const reference to a QVariant. @@ -2909,6 +2914,7 @@ const void *QtPrivate::QVariantTypeCoercer::coerce(const QVariant &value, const /*! \class QVariantConstPointer \since 6.0 + \deprecated [6.15] Use QVariant::ConstPointer instead. \inmodule QtCore \brief Emulated const pointer to QVariant based on a pointer. @@ -2946,6 +2952,7 @@ const QVariant *QVariantConstPointer::operator->() const /*! \class QVariantPointer \since 6.0 + \deprecated [6.15] Use QVariant::Pointer instead. \inmodule QtCore \brief QVariantPointer is a template class that emulates a pointer to QVariant based on a pointer. @@ -2974,6 +2981,9 @@ const QVariant *QVariantConstPointer::operator->() const implement operator->(). */ +QT_WARNING_POP +#endif // QT_DEPRECATED_SINCE(6, 15) + /*! \class QVariant::ConstReference \since 6.11 diff --git a/src/corelib/kernel/qvariant.h b/src/corelib/kernel/qvariant.h index 9117c827afe..19cd1fea7fb 100644 --- a/src/corelib/kernel/qvariant.h +++ b/src/corelib/kernel/qvariant.h @@ -263,7 +263,7 @@ public: ConstReference &operator=(ConstReference &&value) = delete; // To be specialized for each Referred - operator QVariant() const noexcept(Referred::canNoexceptConvertToQVariant); + operator QVariant() const noexcept(Referred::CanNoexceptConvertToQVariant); }; template<typename Referred> @@ -288,18 +288,18 @@ public: ~Reference() = default; Reference &operator=(const Reference &value) - noexcept(Referred::canNoexceptAssignQVariant) + noexcept(Referred::CanNoexceptAssignQVariant) { return operator=(QVariant(value)); } Reference &operator=(Reference &&value) - noexcept(Referred::canNoexceptAssignQVariant) + noexcept(Referred::CanNoexceptAssignQVariant) { return operator=(QVariant(value)); } - operator QVariant() const noexcept(Referred::canNoexceptConvertToQVariant) + operator QVariant() const noexcept(Referred::CanNoexceptConvertToQVariant) { return ConstReference(m_referred); } @@ -313,7 +313,7 @@ public: } // To be specialized for each Referred - Reference &operator=(const QVariant &value) noexcept(Referred::canNoexceptAssignQVariant); + Reference &operator=(const QVariant &value) noexcept(Referred::CanNoexceptAssignQVariant); }; template<typename Pointed> @@ -983,8 +983,13 @@ private: }; } -template<typename Pointer> -class QVariantRef +#if QT_DEPRECATED_SINCE(6, 15) +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED + +template<typename Pointer> class +QT_DEPRECATED_VERSION_X_6_15("Use QVariant::Reference instead.") +QVariantRef { private: const Pointer *m_pointer = nullptr; @@ -1008,20 +1013,23 @@ public: } }; -class Q_CORE_EXPORT QVariantConstPointer +class +QT_DEPRECATED_VERSION_X_6_15("Use QVariant::ConstPointer instead.") +QVariantConstPointer { private: QVariant m_variant; public: - explicit QVariantConstPointer(QVariant variant); + Q_CORE_EXPORT explicit QVariantConstPointer(QVariant variant); - QVariant operator*() const; - const QVariant *operator->() const; + Q_CORE_EXPORT QVariant operator*() const; + Q_CORE_EXPORT const QVariant *operator->() const; }; -template<typename Pointer> -class QVariantPointer +template<typename Pointer> class +QT_DEPRECATED_VERSION_X_6_15("Use QVariant::Pointer instead.") +QVariantPointer { private: const Pointer *m_pointer = nullptr; @@ -1032,6 +1040,9 @@ public: Pointer operator->() const { return *m_pointer; } }; +QT_WARNING_POP +#endif // QT_DEPRECATED_SINCE(6, 15) + QT_END_NAMESPACE #endif // QVARIANT_H diff --git a/src/corelib/mimetypes/qmimeprovider.cpp b/src/corelib/mimetypes/qmimeprovider.cpp index de7043e8c1d..ecd1ac77779 100644 --- a/src/corelib/mimetypes/qmimeprovider.cpp +++ b/src/corelib/mimetypes/qmimeprovider.cpp @@ -512,20 +512,21 @@ QMimeBinaryProvider::MimeTypeExtraMap::const_iterator QMimeBinaryProvider::loadMimeTypeExtra(const QString &mimeName) { #if QT_CONFIG(xmlstreamreader) - auto it = m_mimetypeExtra.find(mimeName); - if (it == m_mimetypeExtra.cend()) { + auto [it, insertionOccurred] = m_mimetypeExtra.try_emplace(mimeName); + if (insertionOccurred) { // load comment and globPatterns // shared-mime-info since 1.3 lowercases the xml files - QString mimeFile = m_directory + u'/' + mimeName.toLower() + ".xml"_L1; - if (!QFile::exists(mimeFile)) - mimeFile = m_directory + u'/' + mimeName + ".xml"_L1; // pre-1.3 - - QFile qfile(mimeFile); - if (!qfile.open(QFile::ReadOnly)) - return m_mimetypeExtra.cend(); + QFile qfile; + const QString mimeFile = m_directory + u'/' + mimeName.toLower() + ".xml"_L1; + qfile.setFileName(mimeFile); + if (!qfile.open(QFile::ReadOnly)) { + const QString fallbackMimeFile = m_directory + u'/' + mimeName + ".xml"_L1; // pre-1.3 + qfile.setFileName(fallbackMimeFile); + if (!qfile.open(QFile::ReadOnly)) + return it; + } - it = m_mimetypeExtra.try_emplace(mimeName).first; MimeTypeExtra &extra = it->second; QString mainPattern; diff --git a/src/corelib/platform/wasm/qstdweb.cpp b/src/corelib/platform/wasm/qstdweb.cpp index 4f3ecc4c6d9..bbaf2c442a0 100644 --- a/src/corelib/platform/wasm/qstdweb.cpp +++ b/src/corelib/platform/wasm/qstdweb.cpp @@ -446,6 +446,119 @@ EventCallback::EventCallback(emscripten::val element, const std::string &name, } +size_t qstdweb::Promise::State::s_numInstances = 0; + +// +// When a promise settles, all attached handlers will be called in +// the order they where added. +// +// In particular a finally handler will be called according to its +// position in the call chain. Which is not necessarily at the end, +// +// This makes cleanup difficult. If we cleanup to early, we will remove +// handlers before they have a chance to be called. This would be the +// case if we add a finally handler in the Promise constructor. +// +// For correct cleanup it is necessary that it happens after the +// last handler has been called. +// +// We choose to implement this by making sure the last handler +// is always a finally handler. +// +// In this case we have multiple finally handlers, so any called +// handler checks if it is the last handler to be called. +// If it is, the cleanup is performed, otherwise cleanup +// is delayed to the last handler. +// +// We could try to let the handlers cleanup themselves, but this +// only works for finally handlers. A then or catch handler is not +// necessarily called, and would not cleanup itself. +// +// We could try to let a (then,catch) pair cleanup both handlers, +// under the assumption that one of them will always be called. +// This does not work in the case that we do not have both handlers, +// or if the then handler throws (both should be called in this case). +// +Promise& Promise::addThenFunction(std::function<void(emscripten::val)> thenFunc) +{ + QWasmSuspendResumeControl *suspendResume = QWasmSuspendResumeControl::get(); + Q_ASSERT(suspendResume); + + m_state->m_handlers.push_back(suspendResume->registerEventHandler(thenFunc)); + m_state->m_promise = + m_state->m_promise.call<emscripten::val>( + "then", + suspendResume->jsEventHandlerAt( + m_state->m_handlers.back())); + + addFinallyFunction([](){}); // Add a potential cleanup handler + return (*this); +} + +Promise& Promise::addCatchFunction(std::function<void(emscripten::val)> catchFunc) +{ + QWasmSuspendResumeControl *suspendResume = QWasmSuspendResumeControl::get(); + Q_ASSERT(suspendResume); + + m_state->m_handlers.push_back(suspendResume->registerEventHandler(catchFunc)); + m_state->m_promise = + m_state->m_promise.call<emscripten::val>( + "catch", + suspendResume->jsEventHandlerAt( + m_state->m_handlers.back())); + + addFinallyFunction([](){}); // Add a potential cleanup handler + return (*this); +} + +Promise& Promise::addFinallyFunction(std::function<void()> finallyFunc) +{ + QWasmSuspendResumeControl *suspendResume = QWasmSuspendResumeControl::get(); + Q_ASSERT(suspendResume); + + auto thisHandler = std::make_shared<uint32_t>((uint32_t)(-1)); + auto state = m_state; + + std::function<void(emscripten::val)> func = + [state, thisHandler, finallyFunc](emscripten::val element) { + Q_UNUSED(element); + + finallyFunc(); + + // See comment at top, we can only do the cleanup + // if we are the last handler in the handler chain + if (state->m_handlers.back() == *thisHandler) { + auto guard = state; // removeEventHandler will remove also this function + QWasmSuspendResumeControl *suspendResume = QWasmSuspendResumeControl::get(); + Q_ASSERT(suspendResume); + for (int i = 0; i < guard->m_handlers.size(); ++i) { + suspendResume->removeEventHandler(guard->m_handlers[i]); + guard->m_handlers[i] = (uint32_t)(-1); + } + } + }; + + *thisHandler = suspendResume->registerEventHandler(func); + m_state->m_handlers.push_back(*thisHandler); + m_state->m_promise = + m_state->m_promise.call<emscripten::val>( + "finally", + suspendResume->jsEventHandlerAt( + m_state->m_handlers.back())); + + return (*this); +} + +void Promise::suspendExclusive() +{ + Promise::suspendExclusive(m_state->m_handlers); +} + +emscripten::val Promise::getPromise() const +{ + return m_state->m_promise; +} + uint32_t Promise::adoptPromise(emscripten::val promise, PromiseCallbacks callbacks, QList<uint32_t> *handlers) { Q_ASSERT_X(!!callbacks.catchFunc || !!callbacks.finallyFunc || !!callbacks.thenFunc, diff --git a/src/corelib/platform/wasm/qstdweb_p.h b/src/corelib/platform/wasm/qstdweb_p.h index b14d9e4012f..07df021c444 100644 --- a/src/corelib/platform/wasm/qstdweb_p.h +++ b/src/corelib/platform/wasm/qstdweb_p.h @@ -237,11 +237,80 @@ namespace qstdweb { std::function<void()> finallyFunc; }; - namespace Promise { - uint32_t Q_CORE_EXPORT adoptPromise(emscripten::val promise, PromiseCallbacks callbacks, QList<uint32_t> *handlers = nullptr); + // Note: it is ok for the Promise object to go out of scope, + // the resources will be cleaned up in the finally handler. + class Q_CORE_EXPORT Promise { + public: + template<typename... Args> + Promise(emscripten::val target, QString methodName, Args... args) { + m_state = std::make_shared<State>(); + m_state->m_promise = target.call<emscripten::val>( + methodName.toStdString().c_str(), std::forward<Args>(args)...); + if (m_state->m_promise.isUndefined() || m_state->m_promise["constructor"]["name"].as<std::string>() != "Promise") { + qFatal("This function did not return a promise"); + } + addFinallyFunction([](){}); + } + + Promise(emscripten::val promise) { + m_state = std::make_shared<State>(); + m_state->m_promise = promise; + if (m_state->m_promise.isUndefined() || m_state->m_promise["constructor"]["name"].as<std::string>() != "Promise") { + qFatal("This function did not return a promise"); + } + addFinallyFunction([](){}); + } + + Promise(const std::vector<Promise> &promises) { + std::vector<emscripten::val> all; + all.reserve(promises.size()); + for (const auto &p : promises) + all.push_back(p.getPromise()); + + auto arr = emscripten::val::array(all); + m_state = std::make_shared<State>(); + m_state->m_promise = emscripten::val::global("Promise").call<emscripten::val>("all", arr); + addFinallyFunction([](){}); + } + + Promise& addThenFunction(std::function<void(emscripten::val)> thenFunc); + Promise& addCatchFunction(std::function<void(emscripten::val)> catchFunc); + Promise& addFinallyFunction(std::function<void()> finallyFunc); + + void suspendExclusive(); + + emscripten::val getPromise() const; + + public: + class State { + private: + friend class Promise; + + State(const State&) = delete; + State(State&&) = delete; + State& operator=(const State&) = delete; + State& operator=(State&&) = delete; + + public: + State() { ++s_numInstances; } + ~State() { --s_numInstances; } + static size_t numInstances() { return s_numInstances; } + + private: + emscripten::val m_promise = emscripten::val::undefined(); + QList<uint32_t> m_handlers; + static size_t s_numInstances; + }; + + private: + std::shared_ptr<State> m_state; + + public: + // Deprecated: To be backwards compatible + static uint32_t Q_CORE_EXPORT adoptPromise(emscripten::val promise, PromiseCallbacks callbacks, QList<uint32_t> *handlers = nullptr); template<typename... Args> - uint32_t make(emscripten::val target, + static uint32_t make(emscripten::val target, QString methodName, PromiseCallbacks callbacks, Args... args) @@ -256,7 +325,7 @@ namespace qstdweb { } template<typename... Args> - void make( + static void make( QList<uint32_t> &handlers, emscripten::val target, QString methodName, @@ -272,8 +341,8 @@ namespace qstdweb { adoptPromise(std::move(promiseObject), std::move(callbacks), &handlers); } - void Q_CORE_EXPORT suspendExclusive(QList<uint32_t> handlerIndices); - void Q_CORE_EXPORT all(std::vector<emscripten::val> promises, PromiseCallbacks callbacks); + static void Q_CORE_EXPORT suspendExclusive(QList<uint32_t> handlerIndices); + static void Q_CORE_EXPORT all(std::vector<emscripten::val> promises, PromiseCallbacks callbacks); }; template<class F> diff --git a/src/corelib/platform/windows/qbstr_p.h b/src/corelib/platform/windows/qbstr_p.h index 21eecfe2df7..32b1aace76f 100644 --- a/src/corelib/platform/windows/qbstr_p.h +++ b/src/corelib/platform/windows/qbstr_p.h @@ -1,5 +1,6 @@ // Copyright (C) 2024 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default #ifndef QBSTR_P_H #define QBSTR_P_H @@ -141,4 +142,4 @@ QT_END_NAMESPACE #endif // Q_OS_WIN -#endif // QCOMPTR_P_H +#endif // QBSTR_P_H diff --git a/src/corelib/platform/windows/qcomobject_p.h b/src/corelib/platform/windows/qcomobject_p.h index bd6f81d1c28..b7a9c56555d 100644 --- a/src/corelib/platform/windows/qcomobject_p.h +++ b/src/corelib/platform/windows/qcomobject_p.h @@ -1,5 +1,6 @@ // Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default #ifndef QCOMOBJECT_P_H #define QCOMOBJECT_P_H diff --git a/src/corelib/platform/windows/qcomptr_p.h b/src/corelib/platform/windows/qcomptr_p.h index 2a69e7b6038..e640528d3c2 100644 --- a/src/corelib/platform/windows/qcomptr_p.h +++ b/src/corelib/platform/windows/qcomptr_p.h @@ -1,5 +1,6 @@ // Copyright (C) 2024 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default #ifndef QCOMPTR_P_H #define QCOMPTR_P_H diff --git a/src/corelib/platform/windows/qcomvariant_p.h b/src/corelib/platform/windows/qcomvariant_p.h index 34ce5f179ce..cc4ad104106 100644 --- a/src/corelib/platform/windows/qcomvariant_p.h +++ b/src/corelib/platform/windows/qcomvariant_p.h @@ -1,5 +1,6 @@ // Copyright (C) 2024 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default #ifndef QCOMVARIANT_P_H #define QCOMVARIANT_P_H diff --git a/src/corelib/platform/windows/qfactorycacheregistration.cpp b/src/corelib/platform/windows/qfactorycacheregistration.cpp index 6bd69c66d14..04c81b7b665 100644 --- a/src/corelib/platform/windows/qfactorycacheregistration.cpp +++ b/src/corelib/platform/windows/qfactorycacheregistration.cpp @@ -1,5 +1,6 @@ // Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default #include "qfactorycacheregistration_p.h" diff --git a/src/corelib/platform/windows/qfactorycacheregistration_p.h b/src/corelib/platform/windows/qfactorycacheregistration_p.h index d0b19b995b4..f6e7d9eb064 100644 --- a/src/corelib/platform/windows/qfactorycacheregistration_p.h +++ b/src/corelib/platform/windows/qfactorycacheregistration_p.h @@ -1,5 +1,6 @@ // Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default #ifndef QFACTORYCACHEREGISTRATION_P_H #define QFACTORYCACHEREGISTRATION_P_H diff --git a/src/corelib/platform/windows/qt_winrtbase_p.h b/src/corelib/platform/windows/qt_winrtbase_p.h index 79c2bdf6b1c..69a602a59e0 100644 --- a/src/corelib/platform/windows/qt_winrtbase_p.h +++ b/src/corelib/platform/windows/qt_winrtbase_p.h @@ -1,5 +1,6 @@ // Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default #ifndef QT_WINRTBASE_P_H #define QT_WINRTBASE_P_H diff --git a/src/corelib/serialization/qcborvalue.cpp b/src/corelib/serialization/qcborvalue.cpp index 13d74e591d5..0aeb2b07f47 100644 --- a/src/corelib/serialization/qcborvalue.cpp +++ b/src/corelib/serialization/qcborvalue.cpp @@ -975,6 +975,7 @@ QCborContainerPrivate *QCborContainerPrivate::detach(QCborContainerPrivate *d, q } /*! + \internal Prepare for an insertion at position \a index Detaches and ensures there are at least index entries in the array, padding diff --git a/src/corelib/serialization/qdatastream.cpp b/src/corelib/serialization/qdatastream.cpp index ae3bed5b751..fd2f7faeee5 100644 --- a/src/corelib/serialization/qdatastream.cpp +++ b/src/corelib/serialization/qdatastream.cpp @@ -552,6 +552,7 @@ void QDataStream::setByteOrder(ByteOrder bo) \value Qt_6_9 \value Qt_6_10 \value Qt_6_11 + \value Qt_6_12 \omitvalue Qt_DefaultCompiledVersion \sa setVersion(), version() diff --git a/src/corelib/serialization/qdatastream.h b/src/corelib/serialization/qdatastream.h index 04373fe9c8a..d6fcf17efcd 100644 --- a/src/corelib/serialization/qdatastream.h +++ b/src/corelib/serialization/qdatastream.h @@ -96,8 +96,9 @@ public: Qt_6_9 = Qt_6_7, Qt_6_10 = 23, Qt_6_11 = 24, - Qt_DefaultCompiledVersion = Qt_6_11 -#if QT_VERSION >= QT_VERSION_CHECK(6, 12, 0) + Qt_6_12 = Qt_6_11, + Qt_DefaultCompiledVersion = Qt_6_12 +#if QT_VERSION >= QT_VERSION_CHECK(6, 13, 0) #error Add the datastream version for this Qt version and update Qt_DefaultCompiledVersion #endif }; diff --git a/src/corelib/serialization/qxmlstream.cpp b/src/corelib/serialization/qxmlstream.cpp index 9cd90fa9d65..ff70289013a 100644 --- a/src/corelib/serialization/qxmlstream.cpp +++ b/src/corelib/serialization/qxmlstream.cpp @@ -3678,7 +3678,7 @@ void QXmlStreamWriter::setStopWritingOnError(bool stop) The error status is never reset. Writes happening after the error occurred may be ignored, even if the error condition is cleared. - \sa error(), errorString(), raiseError(const QString &message), + \sa error(), errorString(), raiseError() */ bool QXmlStreamWriter::hasError() const { @@ -3692,7 +3692,7 @@ bool QXmlStreamWriter::hasError() const QXmlStreamWriter::Error::None. \since 6.10 - \sa errorString(), raiseError(const QString &message), hasError() + \sa errorString(), raiseError(), hasError() */ QXmlStreamWriter::Error QXmlStreamWriter::error() const { @@ -3708,7 +3708,7 @@ QXmlStreamWriter::Error QXmlStreamWriter::error() const a null string. \since 6.10 - \sa error(), raiseError(const QString &message), hasError() + \sa error(), raiseError(), hasError() */ QString QXmlStreamWriter::errorString() const { diff --git a/src/corelib/text/qlocale.cpp b/src/corelib/text/qlocale.cpp index fed09aee45e..18007cacae6 100644 --- a/src/corelib/text/qlocale.cpp +++ b/src/corelib/text/qlocale.cpp @@ -301,6 +301,7 @@ bool operator<(LikelyPair lhs, LikelyPair rhs) } // anonymous namespace /*! + \internal Fill in blank fields of a locale ID. An ID in which some fields are zero stands for any locale that agrees with diff --git a/src/corelib/text/qstringconverter.cpp b/src/corelib/text/qstringconverter.cpp index bf6e776ee0e..896142b4837 100644 --- a/src/corelib/text/qstringconverter.cpp +++ b/src/corelib/text/qstringconverter.cpp @@ -2739,7 +2739,7 @@ QStringList QStringConverter::availableCodecs() May also provide data from residual content that was pending decoding. When there is no residual data to account for, the return's \c error - field will be set to \l {QCharConverter::FinalizeResult::Error::} + field will be set to \l {QStringConverter::FinalizeResultChar::error} {NoError}. If \a out is supplied and non-null, it must have space in which up to @@ -2793,7 +2793,7 @@ auto QStringDecoder::finalize(char16_t *out, qsizetype maxlen) -> FinalizeResult May also provide data from residual content that was pending decoding. When there is no residual data to account for, the return's \c error - field will be set to \l {QCharConverter::FinalizeResult::Error::} + field will be set to \l {QStringConverter::FinalizeResultChar::error} {NoError}. If \a out is supplied and non-null, it must have space in which up to diff --git a/src/corelib/thread/qfuture.qdoc b/src/corelib/thread/qfuture.qdoc index c4d4daae99f..f3f32e20adc 100644 --- a/src/corelib/thread/qfuture.qdoc +++ b/src/corelib/thread/qfuture.qdoc @@ -215,6 +215,20 @@ It's recommended to use it on the QFuture object that represents the entire continuation chain, like it's shown in the example above. + If any of the continuations in the chain executes an asynchronous + computation and returns a QFuture representing it, the \c cancelChain() call + will not be propagated into such nested computation once it is started. + The reason for that is that the future will be available in the continuation + chain only when the outer future is fulfilled, but the cancellation might + happen when both an outer and a nested futures are still waiting for their + computations to be finished. In such cases, the nested future needs to be + captured and canceled explicitly. + + \snippet code/src_corelib_thread_qfuture.cpp 39 + + In this example, if \c runNestedComputation() is already in progress, + it can only be canceled by calling \c {nested.cancel()}. + \sa cancel() */ diff --git a/src/corelib/thread/qfuture_impl.h b/src/corelib/thread/qfuture_impl.h index 371ee524d72..d6c185cd704 100644 --- a/src/corelib/thread/qfuture_impl.h +++ b/src/corelib/thread/qfuture_impl.h @@ -845,7 +845,7 @@ struct UnwrapHandler using NestedType = typename QtPrivate::Future<ResultType>::type; QFutureInterface<NestedType> promise(QFutureInterfaceBase::State::Pending); - outer->then([promise](const QFuture<ResultType> &outerFuture) mutable { + auto chain = outer->then([promise](const QFuture<ResultType> &outerFuture) mutable { // We use the .then([](QFuture<ResultType> outerFuture) {...}) version // (where outerFuture == *outer), to propagate the exception if the // outer future has failed. @@ -883,6 +883,13 @@ struct UnwrapHandler promise.reportCanceled(); promise.reportFinished(); }); + + // Inject the promise into the chain. + // We use a fake function as a continuation, since the promise is + // managed by the outer future + chain.d.setContinuation(ContinuationWrapper(std::move([](const QFutureInterfaceBase &) {})), + promise.d, QFutureInterfaceBase::ContinuationType::Then); + return promise.future(); } }; diff --git a/src/corelib/thread/qfutureinterface.h b/src/corelib/thread/qfutureinterface.h index 0b88013800e..ff17560d3a1 100644 --- a/src/corelib/thread/qfutureinterface.h +++ b/src/corelib/thread/qfutureinterface.h @@ -42,6 +42,8 @@ template<class Function, class ResultType> class FailureHandler; #endif +struct UnwrapHandler; + #if QT_CORE_REMOVED_SINCE(6, 10) void Q_CORE_EXPORT watchContinuationImpl(const QObject *context, QtPrivate::QSlotObjectBase *slotObj, @@ -187,6 +189,8 @@ private: friend class QtPrivate::FailureHandler; #endif + friend struct QtPrivate::UnwrapHandler; + #if QT_CORE_REMOVED_SINCE(6, 10) friend Q_CORE_EXPORT void QtPrivate::watchContinuationImpl( const QObject *context, QtPrivate::QSlotObjectBase *slotObj, QFutureInterfaceBase &fi); diff --git a/src/corelib/thread/qreadwritelock.cpp b/src/corelib/thread/qreadwritelock.cpp index 96e35dcb965..2a1af2315ca 100644 --- a/src/corelib/thread/qreadwritelock.cpp +++ b/src/corelib/thread/qreadwritelock.cpp @@ -234,14 +234,14 @@ QBasicReadWriteLock::contendedTryLockForRead(QDeadlineTimer timeout, void *dd) return d->recursiveLockForRead(timeout); auto lock = qt_unique_lock(d->mutex); - if (d != d_ptr.loadRelaxed()) { + if (QReadWriteLockPrivate *dd = d_ptr.loadAcquire(); d != dd) { // d_ptr has changed: this QReadWriteLock was unlocked before we had // time to lock d->mutex. // We are holding a lock to a mutex within a QReadWriteLockPrivate // that is already released (or even is already re-used). That's ok // because the QFreeList never frees them. // Just unlock d->mutex (at the end of the scope) and retry. - d = d_ptr.loadAcquire(); + d = dd; continue; } return d->lockForRead(lock, timeout); @@ -340,11 +340,11 @@ QBasicReadWriteLock::contendedTryLockForWrite(QDeadlineTimer timeout, void *dd) return d->recursiveLockForWrite(timeout); auto lock = qt_unique_lock(d->mutex); - if (d != d_ptr.loadRelaxed()) { + if (QReadWriteLockPrivate *dd = d_ptr.loadAcquire(); d != dd) { // The mutex was unlocked before we had time to lock the mutex. // We are holding to a mutex within a QReadWriteLockPrivate that is already released // (or even is already re-used) but that's ok because the QFreeList never frees them. - d = d_ptr.loadAcquire(); + d = dd; continue; } return d->lockForWrite(lock, timeout); diff --git a/src/corelib/time/qdatetime.cpp b/src/corelib/time/qdatetime.cpp index deac396061d..34e910fabec 100644 --- a/src/corelib/time/qdatetime.cpp +++ b/src/corelib/time/qdatetime.cpp @@ -3894,10 +3894,12 @@ QDateTime::Data QDateTimePrivate::create(QDate toDate, QTime toTime, const QTime Selects a time on the standard time side of the transition. \value PreferDaylightSaving Selects a time on the daylight-saving-time side of the transition. - \value LegacyBehavior - An alias for RelativeToBefore, which is used as default for - TransitionResolution parameters, as this most closely matches the - behavior prior to Qt 6.7. + \omitvalue LegacyBehavior + + An additional constant, \c LegacyBehavior, is used as a default value for + TransitionResolution parameters in some constructors and setter functions. + This is an alias for \c RelativeToBefore, which implements behavior that + most closely matches the behavior of QDateTime prior to Qt 6.7. For \l addDays(), \l addMonths() or \l addYears(), the behavior is and (mostly) was to use \c RelativeToBefore if adding a positive adjustment and \c @@ -3909,7 +3911,7 @@ QDateTime::Data QDateTimePrivate::create(QDate toDate, QTime toTime, const QTime where the daylight-saving mechanism is a decrease in offset from UTC in winter (known as "negative DST"), the reverse applies, provided the operating system reports - as it does on most platforms - whether a datetime - is in DST or standard time. For some platforms, where transition times are + is in DST or standard time. For some platforms, where transition details are unavailable even for Qt::TimeZone datetimes, QTimeZone is obliged to presume that the side with lower offset from UTC is standard time, effectively assuming positive DST. diff --git a/src/corelib/time/qdatetimeparser.cpp b/src/corelib/time/qdatetimeparser.cpp index 10882738a39..05ba3d2beae 100644 --- a/src/corelib/time/qdatetimeparser.cpp +++ b/src/corelib/time/qdatetimeparser.cpp @@ -2389,6 +2389,7 @@ bool operator==(QDateTimeParser::SectionNode s1, QDateTimeParser::SectionNode s2 } /*! + \internal Sets \a cal as the calendar to use. The default is Gregorian. */ diff --git a/src/corelib/tools/qarraydata.cpp b/src/corelib/tools/qarraydata.cpp index 1c0371e463e..d173d824e88 100644 --- a/src/corelib/tools/qarraydata.cpp +++ b/src/corelib/tools/qarraydata.cpp @@ -162,7 +162,7 @@ allocateHelper(QArrayData **dptr, qsizetype objectSize, qsizetype alignment, qsi QArrayData::AllocationOption option) noexcept { *dptr = nullptr; - if (capacity == 0) + if (capacity <= 0) return {}; const qsizetype headerSize = calculateHeaderSize(alignment); diff --git a/src/corelib/tools/qeasingcurve.cpp b/src/corelib/tools/qeasingcurve.cpp index de68a0042ac..ce35e8ccffe 100644 --- a/src/corelib/tools/qeasingcurve.cpp +++ b/src/corelib/tools/qeasingcurve.cpp @@ -332,8 +332,6 @@ struct TCBPoint qreal _c; qreal _b; - TCBPoint() {} - TCBPoint(QPointF point, qreal t, qreal c, qreal b) : _point(point), _t(t), _c(c), _b(b) {} bool operator==(const TCBPoint &other) const { @@ -1381,7 +1379,7 @@ void QEasingCurve::addTCBSegment(const QPointF &nextPoint, qreal t, qreal c, qre if (!d_ptr->config) d_ptr->config = curveToFunctionObject(d_ptr->type); - d_ptr->config->_tcbPoints.append(TCBPoint(nextPoint, t, c, b)); + d_ptr->config->_tcbPoints.append(TCBPoint{nextPoint, t, c, b}); if (nextPoint == QPointF(1.0, 1.0)) { d_ptr->config->_bezierCurves = tcbToBezier(d_ptr->config->_tcbPoints); diff --git a/src/corelib/tools/qflatmap_p.h b/src/corelib/tools/qflatmap_p.h index d2c0d45b79d..bdb0e24dde8 100644 --- a/src/corelib/tools/qflatmap_p.h +++ b/src/corelib/tools/qflatmap_p.h @@ -15,7 +15,9 @@ // We mean it. // +#include <QtCore/qcontainertools_impl.h> #include "qlist.h" +#include <QtCore/qtclasshelpermacros.h> #include "private/qglobal_p.h" #include <algorithm> @@ -42,23 +44,9 @@ QT_BEGIN_NAMESPACE QFlatMap<float, int, std::less<float>, std::vector<float>, std::vector<int>> */ -// Qt 6.4: -// - removed QFlatMap API which was incompatible with STL semantics -// - will be released with said API disabled, to catch any out-of-tree users -// - also allows opting in to the new API using QFLATMAP_ENABLE_STL_COMPATIBLE_INSERT -// Qt 6.5 -// - will make QFLATMAP_ENABLE_STL_COMPATIBLE_INSERT the default: - -#ifndef QFLATMAP_ENABLE_STL_COMPATIBLE_INSERT -# if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) -# define QFLATMAP_ENABLE_STL_COMPATIBLE_INSERT -# endif -#endif - namespace Qt { -struct OrderedUniqueRange_t {}; -constexpr OrderedUniqueRange_t OrderedUniqueRange = {}; +QT_DEFINE_TAG(OrderedUniqueRange); } // namespace Qt @@ -83,35 +71,11 @@ public: } }; -namespace qflatmap { -namespace detail { -template <class T> -class QFlatMapMockPointer -{ - T ref; -public: - QFlatMapMockPointer(T r) - : ref(r) - { - } - - T *operator->() - { - return &ref; - } -}; -} // namespace detail -} // namespace qflatmap - template<class Key, class T, class Compare = std::less<Key>, class KeyContainer = QList<Key>, class MappedContainer = QList<T>> class QFlatMap : private QFlatMapValueCompare<Key, T, Compare> { static_assert(std::is_nothrow_destructible_v<T>, "Types with throwing destructors are not supported in Qt containers."); - - template<class U> - using mock_pointer = qflatmap::detail::QFlatMapMockPointer<U>; - public: using key_type = Key; using mapped_type = T; @@ -134,7 +98,7 @@ public: using difference_type = ptrdiff_t; using value_type = std::pair<const Key, T>; using reference = std::pair<const Key &, T &>; - using pointer = mock_pointer<reference>; + using pointer = QtPrivate::ArrowProxy<reference>; using iterator_category = std::random_access_iterator_tag; iterator() = default; @@ -266,7 +230,7 @@ public: using difference_type = ptrdiff_t; using value_type = std::pair<const Key, const T>; using reference = std::pair<const Key &, const T &>; - using pointer = mock_pointer<reference>; + using pointer = QtPrivate::ArrowProxy<reference>; using iterator_category = std::random_access_iterator_tag; const_iterator() = default; @@ -415,7 +379,6 @@ private: public: QFlatMap() = default; -#ifdef QFLATMAP_ENABLE_STL_COMPATIBLE_INSERT explicit QFlatMap(const key_container_type &keys, const mapped_container_type &values) : c{keys, values} { @@ -451,7 +414,6 @@ public: initWithRange(first, last); ensureOrderedUnique(); } -#endif explicit QFlatMap(Qt::OrderedUniqueRange_t, const key_container_type &keys, const mapped_container_type &values) @@ -493,7 +455,6 @@ public: { } -#ifdef QFLATMAP_ENABLE_STL_COMPATIBLE_INSERT explicit QFlatMap(const key_container_type &keys, const mapped_container_type &values, const Compare &compare) : value_compare(compare), c{keys, values} @@ -534,7 +495,6 @@ public: initWithRange(first, last); ensureOrderedUnique(); } -#endif explicit QFlatMap(Qt::OrderedUniqueRange_t, const key_container_type &keys, const mapped_container_type &values, const Compare &compare) @@ -649,14 +609,18 @@ public: T value(const Key &key) const { auto it = find(key); - return it == end() ? T() : it.value(); + if (it == end()) + return T(); + return it.value(); } template <class X, class Y = Compare, is_marked_transparent<Y> = nullptr> T value(const X &key) const { auto it = find(key); - return it == end() ? T() : it.value(); + if (it == end()) + return T(); + return it.value(); } T &operator[](const Key &key) @@ -674,7 +638,6 @@ public: return value(key); } -#ifdef QFLATMAP_ENABLE_STL_COMPATIBLE_INSERT std::pair<iterator, bool> insert(const Key &key, const T &value) { return try_emplace(key, value); @@ -694,7 +657,6 @@ public: { return try_emplace(std::move(key), std::move(value)); } -#endif template <typename...Args> std::pair<iterator, bool> try_emplace(const Key &key, Args&&...args) @@ -738,7 +700,6 @@ public: return r; } -#ifdef QFLATMAP_ENABLE_STL_COMPATIBLE_INSERT template <class InputIt, is_compatible_iterator<InputIt> = nullptr> void insert(InputIt first, InputIt last) { @@ -764,7 +725,6 @@ public: { insertOrderedUniqueRange(first, last); } -#endif iterator begin() { return { &c, 0 }; } const_iterator begin() const { return { &c, 0 }; } @@ -943,12 +903,13 @@ private: T do_take(iterator it) { - if (it != end()) { + if (it == end()) + return {}; + return [&] { T result = std::move(it.value()); erase(it); return result; - } - return {}; + }(); } template <class InputIt, is_compatible_iterator<InputIt> = nullptr> diff --git a/src/corelib/tools/qlist.h b/src/corelib/tools/qlist.h index a11f7913dc7..e69b9aebabb 100644 --- a/src/corelib/tools/qlist.h +++ b/src/corelib/tools/qlist.h @@ -301,21 +301,27 @@ public: explicit QList(qsizetype size) : d(size) { - if (size) + if (size) { + Q_CHECK_PTR(d.data()); d->appendInitialize(size); + } } QList(qsizetype size, parameter_type t) : d(size) { - if (size) + if (size) { + Q_CHECK_PTR(d.data()); d->copyAppend(size, t); + } } inline QList(std::initializer_list<T> args) : d(qsizetype(args.size())) { - if (args.size()) + if (args.size()) { + Q_CHECK_PTR(d.data()); d->copyAppend(args.begin(), args.end()); + } } QList<T> &operator=(std::initializer_list<T> args) @@ -332,6 +338,7 @@ public: const auto distance = std::distance(i1, i2); if (distance) { d = DataPointer(qsizetype(distance)); + Q_CHECK_PTR(d.data()); // appendIteratorRange can deal with contiguous iterators on its own, // this is an optimization for C++17 code. if constexpr (std::is_same_v<std::decay_t<InputIterator>, iterator> || @@ -352,8 +359,10 @@ public: QList(qsizetype size, Qt::Initialization) : d(size) { - if (size) + if (size) { + Q_CHECK_PTR(d.data()); d->appendUninitialized(size); + } } // compiler-generated special member functions are fine! @@ -823,7 +832,10 @@ void QList<T>::reserve(qsizetype asize) } } - DataPointer detached(qMax(asize, size())); + qsizetype newSize = qMax(asize, size()); + DataPointer detached(newSize); + if (newSize) + Q_CHECK_PTR(detached.data()); detached->copyAppend(d->begin(), d->end()); if (detached.d_ptr()) detached->setFlag(Data::CapacityReserved); @@ -839,6 +851,7 @@ inline void QList<T>::squeeze() // must allocate memory DataPointer detached(size()); if (size()) { + Q_CHECK_PTR(detached.data()); if (d.needsDetach()) detached->copyAppend(d.data(), d.data() + d.size); else diff --git a/src/corelib/tools/qmargins.h b/src/corelib/tools/qmargins.h index f833a338b16..cbdb093adc8 100644 --- a/src/corelib/tools/qmargins.h +++ b/src/corelib/tools/qmargins.h @@ -333,20 +333,13 @@ private: qreal m_right; qreal m_bottom; - QT_WARNING_PUSH - QT_WARNING_DISABLE_FLOAT_COMPARE friend constexpr bool qFuzzyCompare(const QMarginsF &lhs, const QMarginsF &rhs) noexcept { - return ((!lhs.m_left || !rhs.m_left) ? qFuzzyIsNull(lhs.m_left - rhs.m_left) - : qFuzzyCompare(lhs.m_left, rhs.m_left)) - && ((!lhs.m_top || !rhs.m_top) ? qFuzzyIsNull(lhs.m_top - rhs.m_top) - : qFuzzyCompare(lhs.m_top, rhs.m_top)) - && ((!lhs.m_right || !rhs.m_right) ? qFuzzyIsNull(lhs.m_right - rhs.m_right) - : qFuzzyCompare(lhs.m_right, rhs.m_right)) - && ((!lhs.m_bottom || !rhs.m_bottom) ? qFuzzyIsNull(lhs.m_bottom - rhs.m_bottom) - : qFuzzyCompare(lhs.m_bottom, rhs.m_bottom)); + return QtPrivate::fuzzyCompare(lhs.m_left, rhs.m_left) + && QtPrivate::fuzzyCompare(lhs.m_top, rhs.m_top) + && QtPrivate::fuzzyCompare(lhs.m_right, rhs.m_right) + && QtPrivate::fuzzyCompare(lhs.m_bottom, rhs.m_bottom); } - QT_WARNING_POP friend constexpr bool qFuzzyIsNull(const QMarginsF &m) noexcept { return qFuzzyIsNull(m.m_left) && qFuzzyIsNull(m.m_top) diff --git a/src/corelib/tools/qpoint.h b/src/corelib/tools/qpoint.h index ae896ba7079..1b767324058 100644 --- a/src/corelib/tools/qpoint.h +++ b/src/corelib/tools/qpoint.h @@ -259,14 +259,11 @@ public: } private: - QT_WARNING_PUSH - QT_WARNING_DISABLE_FLOAT_COMPARE friend constexpr bool qFuzzyCompare(const QPointF &p1, const QPointF &p2) noexcept { - return ((!p1.xp || !p2.xp) ? qFuzzyIsNull(p1.xp - p2.xp) : qFuzzyCompare(p1.xp, p2.xp)) - && ((!p1.yp || !p2.yp) ? qFuzzyIsNull(p1.yp - p2.yp) : qFuzzyCompare(p1.yp, p2.yp)); + return QtPrivate::fuzzyCompare(p1.xp, p2.xp) + && QtPrivate::fuzzyCompare(p1.yp, p2.yp); } - QT_WARNING_POP friend constexpr bool qFuzzyIsNull(const QPointF &point) noexcept { return qFuzzyIsNull(point.xp) && qFuzzyIsNull(point.yp); diff --git a/src/corelib/tools/qsize.h b/src/corelib/tools/qsize.h index 86509cb6483..680bf2812d3 100644 --- a/src/corelib/tools/qsize.h +++ b/src/corelib/tools/qsize.h @@ -254,16 +254,11 @@ public: inline QSizeF &operator/=(qreal c); private: - QT_WARNING_PUSH - QT_WARNING_DISABLE_FLOAT_COMPARE friend constexpr bool qFuzzyCompare(const QSizeF &s1, const QSizeF &s2) noexcept { - // Cannot use qFuzzyCompare(), because it will give incorrect results - // if one of the arguments is 0.0. - return ((!s1.wd || !s2.wd) ? qFuzzyIsNull(s1.wd - s2.wd) : qFuzzyCompare(s1.wd, s2.wd)) - && ((!s1.ht || !s2.ht) ? qFuzzyIsNull(s1.ht - s2.ht) : qFuzzyCompare(s1.ht, s2.ht)); + return QtPrivate::fuzzyCompare(s1.wd, s2.wd) + && QtPrivate::fuzzyCompare(s1.ht, s2.ht); } - QT_WARNING_POP friend constexpr bool qFuzzyIsNull(const QSizeF &size) noexcept { return qFuzzyIsNull(size.wd) && qFuzzyIsNull(size.ht); } friend constexpr bool comparesEqual(const QSizeF &lhs, const QSizeF &rhs) noexcept |
