diff options
-rw-r--r-- | sources/pyside6/PySide6/QtCore/glue/core_snippets.cpp | 99 | ||||
-rw-r--r-- | sources/pyside6/PySide6/QtCore/glue/core_snippets_p.h | 10 | ||||
-rw-r--r-- | sources/pyside6/PySide6/QtCore/typesystem_core_common.xml | 5 | ||||
-rw-r--r-- | sources/pyside6/PySide6/glue/qtcore.cpp | 51 | ||||
-rw-r--r-- | sources/pyside6/libpyside/CMakeLists.txt | 2 | ||||
-rw-r--r-- | sources/pyside6/libpyside/pysidevariantutils.cpp | 212 | ||||
-rw-r--r-- | sources/pyside6/libpyside/pysidevariantutils.h | 37 | ||||
-rw-r--r-- | testing/runner.py | 10 |
8 files changed, 266 insertions, 160 deletions
diff --git a/sources/pyside6/PySide6/QtCore/glue/core_snippets.cpp b/sources/pyside6/PySide6/QtCore/glue/core_snippets.cpp index e58d54998..9b2b40e82 100644 --- a/sources/pyside6/PySide6/QtCore/glue/core_snippets.cpp +++ b/sources/pyside6/PySide6/QtCore/glue/core_snippets.cpp @@ -19,105 +19,6 @@ #include <QtCore/QObject> #include <QtCore/QRegularExpression> #include <QtCore/QStack> -#include <QtCore/QVariant> - -// Helpers for QVariant conversion - -QMetaType QVariant_resolveMetaType(PyTypeObject *type) -{ - if (!PyObject_TypeCheck(type, SbkObjectType_TypeF())) - return {}; - const char *typeName = Shiboken::ObjectType::getOriginalName(type); - if (!typeName) - return {}; - const bool valueType = '*' != typeName[qstrlen(typeName) - 1]; - // Do not convert user type of value - if (valueType && Shiboken::ObjectType::isUserType(type)) - return {}; - QMetaType metaType = QMetaType::fromName(typeName); - if (metaType.isValid()) - return metaType; - // Do not resolve types to value type - if (valueType) - return {}; - // Find in base types. First check tp_bases, and only after check tp_base, because - // tp_base does not always point to the first base class, but rather to the first - // that has added any python fields or slots to its object layout. - // See https://mail.python.org/pipermail/python-list/2009-January/520733.html - if (type->tp_bases) { - const auto size = PyTuple_Size(type->tp_bases); - Py_ssize_t i = 0; - // PYSIDE-1887, PYSIDE-86: Skip QObject base class of QGraphicsObject; - // it needs to use always QGraphicsItem as a QVariant type for - // QGraphicsItem::itemChange() to work. - if (qstrcmp(typeName, "QGraphicsObject*") == 0) - ++i; - for ( ; i < size; ++i) { - auto baseType = reinterpret_cast<PyTypeObject *>(PyTuple_GetItem(type->tp_bases, i)); - const QMetaType derived = QVariant_resolveMetaType(baseType); - if (derived.isValid()) - return derived; - } - } else if (type->tp_base) { - return QVariant_resolveMetaType(type->tp_base); - } - return {}; -} - -QVariant QVariant_convertToValueList(PyObject *list) -{ - if (PySequence_Size(list) < 0) { - // clear the error if < 0 which means no length at all - PyErr_Clear(); - return {}; - } - - Shiboken::AutoDecRef element(PySequence_GetItem(list, 0)); - - auto *type = reinterpret_cast<PyTypeObject *>(element.object()); - QMetaType metaType = QVariant_resolveMetaType(type); - if (!metaType.isValid()) - return {}; - - const QByteArray listTypeName = QByteArrayLiteral("QList<") + metaType.name() + '>'; - metaType = QMetaType::fromName(listTypeName); - if (!metaType.isValid()) - return {}; - - Shiboken::Conversions::SpecificConverter converter(listTypeName); - if (!converter) { - qWarning("Type converter for: %s not registered.", listTypeName.constData()); - return {}; - } - - QVariant var(metaType); - converter.toCpp(list, &var); - return var; -} - -bool QVariant_isStringList(PyObject *list) -{ - if (!PySequence_Check(list)) { - // If it is not a list or a derived list class - // we assume that will not be a String list neither. - return false; - } - - if (PySequence_Size(list) < 0) { - // clear the error if < 0 which means no length at all - PyErr_Clear(); - return false; - } - - Shiboken::AutoDecRef fast(PySequence_Fast(list, "Failed to convert QVariantList")); - const Py_ssize_t size = PySequence_Size(fast.object()); - for (Py_ssize_t i = 0; i < size; ++i) { - Shiboken::AutoDecRef item(PySequence_GetItem(fast.object(), i)); - if (PyUnicode_Check(item) == 0) - return false; - } - return true; -} // Helpers for qAddPostRoutine diff --git a/sources/pyside6/PySide6/QtCore/glue/core_snippets_p.h b/sources/pyside6/PySide6/QtCore/glue/core_snippets_p.h index 11e84b291..4c1867a1a 100644 --- a/sources/pyside6/PySide6/QtCore/glue/core_snippets_p.h +++ b/sources/pyside6/PySide6/QtCore/glue/core_snippets_p.h @@ -14,10 +14,8 @@ QT_FORWARD_DECLARE_CLASS(QGenericArgument) QT_FORWARD_DECLARE_CLASS(QGenericReturnArgument) -QT_FORWARD_DECLARE_CLASS(QMetaType) QT_FORWARD_DECLARE_CLASS(QObject) QT_FORWARD_DECLARE_CLASS(QRegularExpression) -QT_FORWARD_DECLARE_CLASS(QVariant); QT_BEGIN_NAMESPACE namespace QtCoreHelper { @@ -26,14 +24,6 @@ class QGenericReturnArgumentHolder; } QT_END_NAMESPACE -// Helpers for QVariant conversion - -QMetaType QVariant_resolveMetaType(PyTypeObject *type); - -QVariant QVariant_convertToValueList(PyObject *list); - -bool QVariant_isStringList(PyObject *list); - // Helpers for qAddPostRoutine namespace PySide { void globalPostRoutineCallback(); diff --git a/sources/pyside6/PySide6/QtCore/typesystem_core_common.xml b/sources/pyside6/PySide6/QtCore/typesystem_core_common.xml index 6fca49c6b..c3130ccb2 100644 --- a/sources/pyside6/PySide6/QtCore/typesystem_core_common.xml +++ b/sources/pyside6/PySide6/QtCore/typesystem_core_common.xml @@ -16,6 +16,7 @@ <extra-includes> <include file-name="pysidemetatype.h" location="global"/> <include file-name="pysideutils.h" location="global"/> <!-- QString conversion --> + <include file-name="pysidevariantutils.h" location="global"/> <!-- QVariant conversion --> <include file-name="signalmanager.h" location="global"/> <include file-name="sbkerrors.h" location="global"/> <!-- QtCoreHelper::QGenericReturnArgumentHolder --> @@ -320,6 +321,7 @@ <extra-includes> <include file-name="optional" location="global"/> <include file-name="pysideqenum.h" location="global"/> + <include file-name="pysidevariantutils.h" location="global"/> </extra-includes> <conversion-rule> <native-to-target file="../glue/qtcore.cpp" snippet="return-qvariant"/> @@ -391,6 +393,9 @@ </object-type> <primitive-type name="QJsonObject"> + <extra-includes> + <include file-name="pysidevariantutils.h" location="global"/> + </extra-includes> <conversion-rule> <native-to-target file="../glue/qtcore.cpp" snippet="return-qjsonobject"/> <target-to-native> diff --git a/sources/pyside6/PySide6/glue/qtcore.cpp b/sources/pyside6/PySide6/glue/qtcore.cpp index 7fbf6a785..cd1462676 100644 --- a/sources/pyside6/PySide6/glue/qtcore.cpp +++ b/sources/pyside6/PySide6/glue/qtcore.cpp @@ -225,49 +225,6 @@ return %out; // @snippet conversion-qmetatype-pytypeobject // @snippet qvariant-conversion -static QVariant QVariant_convertToVariantMap(PyObject *map) -{ - Py_ssize_t pos = 0; - Shiboken::AutoDecRef keys(PyDict_Keys(map)); - if (!QVariant_isStringList(keys)) - return {}; - PyObject *key{}; - PyObject *value{}; - QMap<QString,QVariant> ret; - while (PyDict_Next(map, &pos, &key, &value)) { - QString cppKey = %CONVERTTOCPP[QString](key); - QVariant cppValue = %CONVERTTOCPP[QVariant](value); - ret.insert(cppKey, cppValue); - } - return QVariant(ret); -} -static QVariant QVariant_convertToVariantList(PyObject *list) -{ - if (QVariant_isStringList(list)) { - QList<QString > lst = %CONVERTTOCPP[QList<QString>](list); - return QVariant(QStringList(lst)); - } - QVariant valueList = QVariant_convertToValueList(list); - if (valueList.isValid()) - return valueList; - - if (PySequence_Size(list) < 0) { - // clear the error if < 0 which means no length at all - PyErr_Clear(); - return {}; - } - - QList<QVariant> lst; - Shiboken::AutoDecRef fast(PySequence_Fast(list, "Failed to convert QVariantList")); - const Py_ssize_t size = PySequence_Size(fast.object()); - for (Py_ssize_t i = 0; i < size; ++i) { - Shiboken::AutoDecRef pyItem(PySequence_GetItem(fast.object(), i)); - QVariant item = %CONVERTTOCPP[QVariant](pyItem); - lst.append(item); - } - return QVariant(lst); -} - using SpecificConverter = Shiboken::Conversions::SpecificConverter; static std::optional<SpecificConverter> converterForQtType(const char *typeNameC) @@ -1544,7 +1501,7 @@ if (Shiboken::Enum::check(%in)) { metaType = QMetaType::fromName(typeName); } if (!metaType.isValid()) - metaType = QVariant_resolveMetaType(Py_TYPE(%in)); + metaType = PySide::Variant::resolveMetaType(Py_TYPE(%in)); bool ok = false; if (metaType.isValid()) { @@ -1566,12 +1523,12 @@ if (!ok) // @snippet conversion-sbkobject // @snippet conversion-pydict -QVariant ret = QVariant_convertToVariantMap(%in); +QVariant ret = PySide::Variant::convertToVariantMap(%in); %out = ret.isValid() ? ret : QVariant::fromValue(PySide::PyObjectWrapper(%in)); // @snippet conversion-pydict // @snippet conversion-pylist -QVariant ret = QVariant_convertToVariantList(%in); +QVariant ret = PySide::Variant::convertToVariantList(%in); %out = ret.isValid() ? ret : QVariant::fromValue(PySide::PyObjectWrapper(%in)); // @snippet conversion-pylist @@ -1581,7 +1538,7 @@ QVariant ret = QVariant_convertToVariantList(%in); // @snippet conversion-pyobject // @snippet conversion-qjsonobject-pydict -QVariant dict = QVariant_convertToVariantMap(%in); +QVariant dict = PySide::Variant::convertToVariantMap(%in); QJsonValue val = QJsonValue::fromVariant(dict); %out = val.toObject(); // @snippet conversion-qjsonobject-pydict diff --git a/sources/pyside6/libpyside/CMakeLists.txt b/sources/pyside6/libpyside/CMakeLists.txt index 539f1f329..15ab47494 100644 --- a/sources/pyside6/libpyside/CMakeLists.txt +++ b/sources/pyside6/libpyside/CMakeLists.txt @@ -40,6 +40,7 @@ set(libpyside_HEADERS # installed below pysideslot_p.h pysidestaticstrings.h pysideutils.h + pysidevariantutils.h pysideweakref.h qobjectconnect.h signalmanager.h @@ -59,6 +60,7 @@ set(libpyside_SRC pysidesignal.cpp pysideslot.cpp pysideproperty.cpp + pysidevariantutils.cpp pysideweakref.cpp pyside.cpp pyside_numpy.cpp diff --git a/sources/pyside6/libpyside/pysidevariantutils.cpp b/sources/pyside6/libpyside/pysidevariantutils.cpp new file mode 100644 index 000000000..7dbfb3afc --- /dev/null +++ b/sources/pyside6/libpyside/pysidevariantutils.cpp @@ -0,0 +1,212 @@ +// 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 "pysidevariantutils.h" +#include "pysideutils.h" + +#include <QtCore/qvariantmap.h> + +#include <autodecref.h> +#include <sbkconverter.h> +#include <basewrapper.h> + +using namespace Qt::StringLiterals; + +static const char qVariantTypeName[] = "QVariant"; + +static void warnConverter(const char *name) +{ + qWarning("Type converter for: %s not registered.", name); +} + +// Helper converting each item of a non-empty list using the "QVariant" converter +static std::optional<QVariantList> pyListToVariantListHelper(PyObject *list, Py_ssize_t size) +{ + Q_ASSERT(size > 0); + QVariantList result; + result.reserve(size); + Shiboken::Conversions::SpecificConverter converter(qVariantTypeName); + if (!converter) { + warnConverter(qVariantTypeName); + return std::nullopt; + } + for (Py_ssize_t i = 0; i < size; ++i) { + Shiboken::AutoDecRef pyItem(PySequence_GetItem(list, i)); + QVariant item; + converter.toCpp(pyItem.object(), &item); + result.append(item); + } + return result; +} + +// Helper checking for a sequence of Unicode objects +static bool isStringList(PyObject *list) +{ + const Py_ssize_t size = PySequence_Size(list); + if (size == 0) + return false; + for (Py_ssize_t i = 0; i < size; ++i) { + Shiboken::AutoDecRef item(PySequence_GetItem(list, i)); + if (PyUnicode_Check(item) == 0) + return false; + } + return true; +} + +// Helper to convert to a QStringList +static std::optional<QStringList> listToStringList(PyObject *list) +{ + static const char listType[] = "QList<QString>"; + Shiboken::Conversions::SpecificConverter converter(listType); + if (!converter) { + warnConverter(listType); + return std::nullopt; + } + QStringList result; + converter.toCpp(list, &result); + return result; +} + +// Helper to convert a non-empty, homogenous list using the converter of the first item +static QVariant convertToValueList(PyObject *list) +{ + Q_ASSERT(PySequence_Size(list) >= 0); + + Shiboken::AutoDecRef element(PySequence_GetItem(list, 0)); + + auto *type = reinterpret_cast<PyTypeObject *>(element.object()); + QMetaType metaType = PySide::Variant::resolveMetaType(type); + if (!metaType.isValid()) + return {}; + + const QByteArray listTypeName = QByteArrayLiteral("QList<") + metaType.name() + '>'; + metaType = QMetaType::fromName(listTypeName); + if (!metaType.isValid()) + return {}; + + Shiboken::Conversions::SpecificConverter converter(listTypeName); + if (!converter) { + warnConverter(listTypeName.constData()); + return {}; + } + + QVariant var(metaType); + converter.toCpp(list, &var); + return var; +} + +namespace PySide::Variant +{ + +QMetaType resolveMetaType(PyTypeObject *type) +{ + if (!PyObject_TypeCheck(type, SbkObjectType_TypeF())) + return {}; + const char *typeName = Shiboken::ObjectType::getOriginalName(type); + if (!typeName) + return {}; + const bool valueType = '*' != typeName[qstrlen(typeName) - 1]; + // Do not convert user type of value + if (valueType && Shiboken::ObjectType::isUserType(type)) + return {}; + QMetaType metaType = QMetaType::fromName(typeName); + if (metaType.isValid()) + return metaType; + // Do not resolve types to value type + if (valueType) + return {}; + // Find in base types. First check tp_bases, and only after check tp_base, because + // tp_base does not always point to the first base class, but rather to the first + // that has added any python fields or slots to its object layout. + // See https://mail.python.org/pipermail/python-list/2009-January/520733.html + if (type->tp_bases) { + const auto size = PyTuple_Size(type->tp_bases); + Py_ssize_t i = 0; + // PYSIDE-1887, PYSIDE-86: Skip QObject base class of QGraphicsObject; + // it needs to use always QGraphicsItem as a QVariant type for + // QGraphicsItem::itemChange() to work. + if (qstrcmp(typeName, "QGraphicsObject*") == 0) + ++i; + for ( ; i < size; ++i) { + auto baseType = reinterpret_cast<PyTypeObject *>(PyTuple_GetItem(type->tp_bases, i)); + const QMetaType derived = resolveMetaType(baseType); + if (derived.isValid()) + return derived; + } + return {}; + } + if (type->tp_base != nullptr) + return resolveMetaType(type->tp_base); + return {}; +} + +std::optional<QVariantList> pyListToVariantList(PyObject *list) +{ + if (list == nullptr || PySequence_Check(list) == 0) + return std::nullopt; + const auto size = PySequence_Size(list); + if (size < 0) { // Some infinite (I/O read) thing? - bail out + PyErr_Clear(); + return std::nullopt; + } + if (size == 0) + return QVariantList{}; + return pyListToVariantListHelper(list, size); +} + +QVariant convertToVariantList(PyObject *list) +{ + const auto size = PySequence_Size(list); + if (size < 0) { // Some infinite (I/O read) thing? - bail out + PyErr_Clear(); + return {}; + } + if (size == 0) + return QVariantList{}; + + if (isStringList(list)) { + auto stringListO = listToStringList(list); + if (stringListO.has_value()) + return {stringListO.value()}; + } + + if (QVariant valueList = convertToValueList(list); valueList.isValid()) + return valueList; + + if (auto vlO = pyListToVariantListHelper(list, size); vlO.has_value()) + return vlO.value(); + + return {}; +} + +QVariant convertToVariantMap(PyObject *map) +{ + if (map == nullptr || PyDict_Check(map) == 0) + return {}; + + QVariantMap result; + if (PyDict_Size(map) == 0) + return result; + + Py_ssize_t pos = 0; + Shiboken::AutoDecRef keys(PyDict_Keys(map)); + if (!isStringList(keys)) + return {}; + + Shiboken::Conversions::SpecificConverter converter(qVariantTypeName); + if (!converter) { + warnConverter(qVariantTypeName); + return {}; + } + + PyObject *key{}; + PyObject *value{}; + while (PyDict_Next(map, &pos, &key, &value)) { + QVariant cppValue; + converter.toCpp(value, &cppValue); + result.insert(PySide::pyUnicodeToQString(key), cppValue); + } + return result; +} + +} // namespace PySide::Variant diff --git a/sources/pyside6/libpyside/pysidevariantutils.h b/sources/pyside6/libpyside/pysidevariantutils.h new file mode 100644 index 000000000..b53f7ce82 --- /dev/null +++ b/sources/pyside6/libpyside/pysidevariantutils.h @@ -0,0 +1,37 @@ +// 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 PYSIDEVARIANTUTILS_H +#define PYSIDEVARIANTUTILS_H + +#include <sbkpython.h> + +#include <pysidemacros.h> + +#include <QtCore/qvariant.h> +#include <QtCore/qvariantlist.h> + +#include <optional> + +namespace PySide::Variant +{ + +/// Return a QMetaType for a PyTypeObject for purposes of +/// converting to a QVariant. +PYSIDE_API QMetaType resolveMetaType(PyTypeObject *type); + +/// Convert a heterogenous Python list to a QVariantList by converting each +/// item using the QVariant converter. +PYSIDE_API std::optional<QVariantList> pyListToVariantList(PyObject *list); + +/// Converts a list to a QVariant following the PySide semantics: +/// - A list of strings is returned as QVariant<QStringList> +/// - A list of convertible values is returned as QVariant<QList<Value>> +/// - Remaining types are returned as QVariant(QVariantList) +PYSIDE_API QVariant convertToVariantList(PyObject *list); + +/// Converts a map to a QVariantMap (string keys and QVariant values) +PYSIDE_API QVariant convertToVariantMap(PyObject *map); +} // namespace PySide::Variant + +#endif // PYSIDEVARIANTUTILS_H diff --git a/testing/runner.py b/testing/runner.py index ad1e01d65..4d96fdfc2 100644 --- a/testing/runner.py +++ b/testing/runner.py @@ -20,7 +20,7 @@ this_dir = os.path.dirname(this_file) build_scripts_dir = os.path.abspath(os.path.join(this_dir, "..")) sys.path.append(build_scripts_dir) -from build_scripts.utils import detect_clang +from build_scripts.utils import detect_clang # noqa: E402 class TestRunner: @@ -78,11 +78,12 @@ class TestRunner: Helper for _find_ctest() that finds the ctest binary in a build system file (ninja, Makefile). """ - look_for = "--force-new-ctest-process" + # Looking for a command ending this way: + look_for = "\\ctest.exe" if "win32" in sys.platform else "/ctest" line = None with open(file_name) as makefile: for line in makefile: - if look_for in line: + if look_for in line and line.lstrip().startswith("COMMAND"): break else: # We have probably forgotten to build the tests. @@ -98,7 +99,8 @@ class TestRunner: raise RuntimeError(msg) # the ctest program is on the left to look_for assert line, f"Did not find {look_for}" - ctest = re.search(r'(\S+|"([^"]+)")\s+' + look_for, line).groups() + look = re.escape(look_for) + ctest = re.search(fr'(\S+{look}|"([^"]+{look})")', line).groups() return ctest[1] or ctest[0] def _find_ctest(self): |