diff options
Diffstat (limited to 'sources')
21 files changed, 387 insertions, 205 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/QtXml/typesystem_xml.xml b/sources/pyside6/PySide6/QtXml/typesystem_xml.xml index 089978b6d..93d3c1f56 100644 --- a/sources/pyside6/PySide6/QtXml/typesystem_xml.xml +++ b/sources/pyside6/PySide6/QtXml/typesystem_xml.xml @@ -20,7 +20,7 @@ <enum-type name="ParseOption" flags="ParseOptions" since="6.5"/> <!-- will be replaced in inject code --> - <value-type name="ParseResult"/> + <value-type name="ParseResult" operator-bool="yes"/> <modify-function signature="setContent(const QByteArray&,bool,QString*,int*,int*)"> <modify-argument index="3"> @@ -130,6 +130,8 @@ </modify-argument> <inject-code class="target" position="beginning" file="../glue/qtxml.cpp" snippet="qdomdocument-setcontent" /> </modify-function> + <declare-function signature="setContent(const QByteArray&@data@, QDomDocument::ParseOptions@options@=QDomDocument::ParseOption::Default)" + return-type="QDomDocument::ParseResult" since="6.8" /> </value-type> <value-type name="QDomDocumentFragment"/> 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/sources/pyside6/tests/QtXml/qdomdocument_test.py b/sources/pyside6/tests/QtXml/qdomdocument_test.py index 8fe4f6e17..b321b1bdf 100644 --- a/sources/pyside6/tests/QtXml/qdomdocument_test.py +++ b/sources/pyside6/tests/QtXml/qdomdocument_test.py @@ -44,18 +44,20 @@ class QDomDocumentTest(unittest.TestCase): def testQDomDocumentSetContentWithBadXmlData(self): '''Sets invalid xml as the QDomDocument contents.''' - ok, errorStr, errorLine, errorColumn = self.dom.setContent(self.badXmlData, True) - self.assertFalse(ok) - self.assertEqual(errorStr, 'Opening and ending tag mismatch.') - self.assertEqual(errorLine, 4) + parseResult = self.dom.setContent(self.badXmlData, + QDomDocument.ParseOption.UseNamespaceProcessing) + self.assertFalse(parseResult) + self.assertEqual(parseResult.errorMessage, 'Opening and ending tag mismatch.') + self.assertEqual(parseResult.errorLine, 4) def testQDomDocumentSetContentWithGoodXmlData(self): '''Sets valid xml as the QDomDocument contents.''' - ok, errorStr, errorLine, errorColumn = self.dom.setContent(self.goodXmlData, True) - self.assertTrue(ok) - self.assertEqual(errorStr, '') - self.assertEqual(errorLine, 0) - self.assertEqual(errorColumn, 0) + parseResult = self.dom.setContent(self.goodXmlData, + QDomDocument.ParseOption.UseNamespaceProcessing) + self.assertTrue(parseResult) + self.assertEqual(parseResult.errorMessage, '') + self.assertEqual(parseResult.errorLine, 0) + self.assertEqual(parseResult.errorColumn, 0) def testQDomDocumentData(self): '''Checks the QDomDocument elements for the valid xml contents.''' @@ -66,7 +68,8 @@ class QDomDocumentTest(unittest.TestCase): self.assertTrue(element.hasAttribute(attribute)) self.assertEqual(element.attribute(attribute), value) - ok, errorStr, errorLine, errorColumn = self.dom.setContent(self.goodXmlData, True) + parseResult = self.dom.setContent(self.goodXmlData, # noqa F:841 + QDomDocument.ParseOption.UseNamespaceProcessing) root = self.dom.documentElement() self.assertEqual(root.tagName(), 'typesystem') checkAttribute(root, 'package', 'PySide6.QtXml') diff --git a/sources/shiboken6/ApiExtractor/abstractmetabuilder.cpp b/sources/shiboken6/ApiExtractor/abstractmetabuilder.cpp index 27570ec4f..fb5399baa 100644 --- a/sources/shiboken6/ApiExtractor/abstractmetabuilder.cpp +++ b/sources/shiboken6/ApiExtractor/abstractmetabuilder.cpp @@ -2664,7 +2664,7 @@ std::optional<AbstractMetaType> qsizetype i = d ? d->m_scopes.size() - 1 : -1; while (i >= 0) { typeInfo = TypeInfo::resolveType(_typei, d->m_scopes.at(i--)); - if (typeInfo.qualifiedName().join(u"::"_s) != _typei.qualifiedName().join(u"::"_s)) + if (typeInfo.qualifiedName() != _typei.qualifiedName()) break; } @@ -2757,12 +2757,22 @@ std::optional<AbstractMetaType> // 4. Special case QFlags (include instantiation in name) if (qualifiedName == u"QFlags") { - qualifiedName = typeInfo.toString(); + qualifiedName = typeInfo.qualifiedInstantationName(); typeInfo.clearInstantiations(); } TypeEntryCList types = findTypeEntries(qualifiedName, name, flags, currentClass, d, errorMessageIn); + if (types.isEmpty() && !typeInfo.instantiations().isEmpty()) { + // Allow for specifying template specializations as primitive types + // with converters ('std::optional<int>' or similar). + auto pt = TypeDatabase::instance()->findPrimitiveType(typeInfo.qualifiedInstantationName()); + if (pt) { + types.append(pt); + typeInfo.clearInstantiations(); + } + } + if (!flags.testFlag(AbstractMetaBuilder::TemplateArgument)) { // Avoid clashes between QByteArray and enum value QMetaType::QByteArray // unless we are looking for template arguments. @@ -2796,7 +2806,7 @@ std::optional<AbstractMetaType> // For non-type template parameters, create a dummy type entry on the fly // as is done for classes. if (!targType.has_value()) { - const QString value = ti.qualifiedName().join(u"::"_s); + const QString value = ti.qualifiedNameString(); if (isNumber(value)) { auto module = typeSystemTypeEntry(type); TypeDatabase::instance()->addConstantValueTypeEntry(value, module); @@ -3082,7 +3092,7 @@ AbstractMetaClassPtr QString prefix = i > 0 ? QStringList(scope.mid(0, i)).join(u"::"_s) + u"::"_s : QString(); QString completeName = prefix + name; const TypeInfo parsed = TypeParser::parse(completeName, &errorMessage); - QString qualifiedName = parsed.qualifiedName().join(u"::"_s); + QString qualifiedName = parsed.qualifiedNameString(); if (qualifiedName.isEmpty()) { qWarning().noquote().nospace() << "Unable to parse type \"" << completeName << "\" while looking for template \"" << name << "\": " << errorMessage; @@ -3196,7 +3206,7 @@ static std::optional<AbstractMetaType> const AbstractMetaClassCPtr &templateClass, const TypeInfo &info, QString *errorMessage) { - QString typeName = info.qualifiedName().join("::"_L1); + QString typeName = info.qualifiedNameString(); TypeDatabase *typeDb = TypeDatabase::instance(); TypeEntryPtr t; // Check for a non-type template integer parameter, that is, for a base diff --git a/sources/shiboken6/ApiExtractor/parser/typeinfo.cpp b/sources/shiboken6/ApiExtractor/parser/typeinfo.cpp index 845a0f916..93627e6d5 100644 --- a/sources/shiboken6/ApiExtractor/parser/typeinfo.cpp +++ b/sources/shiboken6/ApiExtractor/parser/typeinfo.cpp @@ -110,6 +110,28 @@ QStringList TypeInfo::qualifiedName() const return d->m_qualifiedName; } +QString TypeInfo::qualifiedNameString() const +{ + return d->m_qualifiedName.join("::"_L1); +} + +QString TypeInfo::qualifiedInstantationName() const +{ + QString result = qualifiedNameString(); + if (const auto instantiationCount = d->m_instantiations.size()) { + result += u'<'; + for (qsizetype i = 0; i < instantiationCount; ++i) { + if (i) + result += ", "_L1; + result += d->m_instantiations.at(i).toString(); + } + if (result.endsWith(u'>')) + result += u' '; + result += u'>'; + } + return result; +} + void TypeInfo::setQualifiedName(const QStringList &qualified_name) { if (d->m_qualifiedName != qualified_name) @@ -300,7 +322,7 @@ TypeInfo TypeInfo::resolveType(const CodeModelItem &__item, TypeInfo const &__ty // typedef struct xcb_connection_t xcb_connection_t; if (nextItem.get() ==__item.get()) { std::cerr << "** WARNING Bailing out recursion of " << __FUNCTION__ - << "() on " << qPrintable(__type.qualifiedName().join(u"::"_s)) + << "() on " << qPrintable(__type.qualifiedNameString()) << '\n'; return otherType; } @@ -385,19 +407,7 @@ QString TypeInfo::toString() const if (isVolatile()) tmp += u"volatile "_s; - tmp += d->m_qualifiedName.join(u"::"_s); - - if (const auto instantiationCount = d->m_instantiations.size()) { - tmp += u'<'; - for (qsizetype i = 0; i < instantiationCount; ++i) { - if (i) - tmp += u", "_s; - tmp += d->m_instantiations.at(i).toString(); - } - if (tmp.endsWith(u'>')) - tmp += u' '; - tmp += u'>'; - } + tmp += qualifiedInstantationName(); for (Indirection i : d->m_indirections) tmp.append(indirectionKeyword(i)); diff --git a/sources/shiboken6/ApiExtractor/parser/typeinfo.h b/sources/shiboken6/ApiExtractor/parser/typeinfo.h index 9cd746e85..6f75b5737 100644 --- a/sources/shiboken6/ApiExtractor/parser/typeinfo.h +++ b/sources/shiboken6/ApiExtractor/parser/typeinfo.h @@ -39,6 +39,11 @@ public: QStringList qualifiedName() const; void setQualifiedName(const QStringList &qualified_name); + // Returns "std::list" + QString qualifiedNameString() const; + // Returns qualifiedName() with instantions ("std::list<int>") + QString qualifiedInstantationName() const; + void addName(const QString &); bool isVoid() const; diff --git a/sources/shiboken6/doc/typesystem_converters.rst b/sources/shiboken6/doc/typesystem_converters.rst index ab6fba930..f34f5b829 100644 --- a/sources/shiboken6/doc/typesystem_converters.rst +++ b/sources/shiboken6/doc/typesystem_converters.rst @@ -16,7 +16,8 @@ C++ and vice-versa. // C++ class struct Complex { - Complex(double real, double imag); + explicit Complex(double real, double imag); + double real() const; double imag() const; }; @@ -82,8 +83,9 @@ Here's how to do it: <!-- Code injection at module level. --> <inject-code class="native" position="beginning"> - static bool Check2TupleOfNumbers(PyObject* pyIn) { - if (!PySequence_Check(pyIn) || !(PySequence_Size(pyIn) == 2)) + static bool Check2TupleOfNumbers(PyObject *pyIn) + { + if (PySequence_Check(pyIn) == 0 || PySequence_Size(pyIn) != 2) return false; Shiboken::AutoDecRef pyReal(PySequence_GetItem(pyIn, 0)); if (!PyNumber_Check(pyReal)) @@ -134,7 +136,8 @@ Container Conversions Converters for :ref:`container-type <container-type>` are pretty much the same as for other type, except that they make use of the type system variables -:ref:`%INTYPE_# <intype_n>` and :ref:`%OUTTYPE_# <outtype_n>`. +:ref:`%INTYPE_# <intype_n>` and :ref:`%OUTTYPE_# <outtype_n>` denoting the +template parameters. |project| combines the conversion code for containers with the conversion defined (or automatically generated) for the containers. @@ -147,13 +150,12 @@ defined (or automatically generated) for the containers. <native-to-target> PyObject* %out = PyDict_New(); - %INTYPE::const_iterator it = %in.begin(); - for (; it != %in.end(); ++it) { - %INTYPE_0 key = it->first; - %INTYPE_1 value = it->second; - PyDict_SetItem(%out, + for (auto it = %in.cbegin(), end = %in.cend(); it != end; ++it) { + const auto &key = it->first; + const auto &value = it->second; + PyDict_SetItem(%out, %CONVERTTOPYTHON[%INTYPE_0](key), - %CONVERTTOPYTHON[%INTYPE_1](value)); + %CONVERTTOPYTHON[%INTYPE_1](value)); } return %out; </native-to-target> @@ -161,8 +163,8 @@ defined (or automatically generated) for the containers. <target-to-native> <add-conversion type="PyDict"> - PyObject* key; - PyObject* value; + PyObject *key{}; + PyObject *value{}; Py_ssize_t pos = 0; while (PyDict_Next(%in, &pos, &key, &value)) { %OUTTYPE_0 cppKey = %CONVERTTOCPP[%OUTTYPE_0](key); @@ -183,10 +185,10 @@ defined (or automatically generated) for the containers. For this case, a number of pre-defined conversion templates are provided (see :ref:`predefined_templates`). -.. _variables_and_functions: +.. _converter_variables_and_functions: -Variables & Functions -===================== +Converter Variables & Functions +=============================== .. _in: @@ -212,7 +214,7 @@ Variables & Functions .. _intype_n: **%INTYPE_#** - Replaced by the name of the #th type used in a container. + Replaced by the name of the #th template parameter type used in a container. .. _outtype: @@ -225,7 +227,7 @@ Variables & Functions .. _outtype_n: **%OUTTYPE_#** - Replaced by the name of the #th type used in a container. + Replaced by the name of the #th template parameter type used in a container. .. _checktype: diff --git a/sources/shiboken6/doc/typesystem_specifying_types.rst b/sources/shiboken6/doc/typesystem_specifying_types.rst index c03d203b9..60ac5c6d9 100644 --- a/sources/shiboken6/doc/typesystem_specifying_types.rst +++ b/sources/shiboken6/doc/typesystem_specifying_types.rst @@ -208,6 +208,9 @@ can be generated for them. Instead, an instance of the viewed class should be instantiated and passed to functions using the view class for argument types. +It is also possible to specify template specializations +like "std::optional<int>" as primitive types with converters. + See :ref:`predefined_templates` for built-in templates for standard type conversion rules. diff --git a/sources/shiboken6/doc/typesystem_variables.rst b/sources/shiboken6/doc/typesystem_variables.rst index 5eb5d5abe..6dfd1f801 100644 --- a/sources/shiboken6/doc/typesystem_variables.rst +++ b/sources/shiboken6/doc/typesystem_variables.rst @@ -16,6 +16,8 @@ implementation specifics. Variables ========= +In addition to the below listed variables, there are some variables specific to type +conversion code (see :ref:`converter_variables_and_functions`). .. _cpp_return_argument: diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/layout.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/layout.py index 109562a98..8eea431c3 100644 --- a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/layout.py +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/layout.py @@ -352,6 +352,7 @@ def create_signature_union(props, key): if last == _VAR_POSITIONAL: kind = _KEYWORD_ONLY if default is None: + ann = typing.Union[ann] ann = typing.Optional[ann] if default is not _empty and layout.ellipsis: default = ellipsis @@ -373,6 +374,7 @@ def create_signature_union(props, key): ret_anno = annotations.get('return', _empty) if ret_anno is not _empty and props["fullname"] in missing_optional_return: + ret_anno = typing.Union[ret_anno] ret_anno = typing.Optional[ret_anno] return inspect.Signature(params, return_annotation=ret_anno, __validate_parameters__=False) diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/pyi_generator.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/pyi_generator.py index c5dc44644..1031e0bb0 100644 --- a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/pyi_generator.py +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/pyi_generator.py @@ -69,12 +69,12 @@ class Formatter(Writer): backup = inspect.formatannotation @classmethod - def formatannotation(cls, annotation, base_module=None): + def formatannotation(cls, annotation, base_module=None, *args, **kwargs): if getattr(annotation, '__module__', None) == 'typing': # do not remove the prefix! return repr(annotation) # do the normal action. - return cls.backup(annotation, base_module) + return cls.backup(annotation, base_module, *args, **kwargs) @classmethod def fix_typing_prefix(cls, signature): diff --git a/sources/shiboken6/tests/libsample/samplenamespace.cpp b/sources/shiboken6/tests/libsample/samplenamespace.cpp index 18a18d28d..3836f43aa 100644 --- a/sources/shiboken6/tests/libsample/samplenamespace.cpp +++ b/sources/shiboken6/tests/libsample/samplenamespace.cpp @@ -97,6 +97,15 @@ int passReferenceToObjectType(const ObjectType &obj, int multiplier) return obj.objectName().size() * multiplier; } +// Exercise specifying complete template specializations as primitive types. +std::optional<long> optionalMultiply(const std::optional<long> &v1, + const std::optional<long> &v2) +{ + if (!v1.has_value() || !v2.has_value()) + return std::nullopt; + return v1.value() * v2.value(); +} + int variableInNamespace = 42; } // namespace SampleNamespace diff --git a/sources/shiboken6/tests/libsample/samplenamespace.h b/sources/shiboken6/tests/libsample/samplenamespace.h index 99a0787ee..63dc2f316 100644 --- a/sources/shiboken6/tests/libsample/samplenamespace.h +++ b/sources/shiboken6/tests/libsample/samplenamespace.h @@ -9,6 +9,7 @@ #include "point.h" #include "objecttype.h" +#include <optional> #include <list> // Anonymous global enum @@ -157,6 +158,9 @@ LIBSAMPLE_API double passReferenceToValueType(const Point &point, double multipl // Add a new signature on type system with only a ObjectType pointer as parameter. LIBSAMPLE_API int passReferenceToObjectType(const ObjectType &obj, int multiplier); +LIBSAMPLE_API std::optional<long> optionalMultiply(const std::optional<long> &v1, + const std::optional<long> &v2); + extern LIBSAMPLE_API int variableInNamespace; } // namespace SampleNamespace diff --git a/sources/shiboken6/tests/samplebinding/sample_test.py b/sources/shiboken6/tests/samplebinding/sample_test.py index c003ad398..43b84d36e 100644 --- a/sources/shiboken6/tests/samplebinding/sample_test.py +++ b/sources/shiboken6/tests/samplebinding/sample_test.py @@ -79,6 +79,13 @@ class ModuleTest(unittest.TestCase): mo2 = sample.MoveOnlyHandler.passMoveOnly(mo) self.assertEqual(mo2.value(), v) + def testOptionalLong(self): + v1 = 2 + v2 = 3 + self.assertEqual(sample.SampleNamespace.optionalMultiply(v1, v2), 6) + self.assertIsNone(sample.SampleNamespace.optionalMultiply(v1, None)) + self.assertIsNone(sample.SampleNamespace.optionalMultiply(None, v2)) + if __name__ == '__main__': unittest.main() diff --git a/sources/shiboken6/tests/samplebinding/typesystem_sample.xml b/sources/shiboken6/tests/samplebinding/typesystem_sample.xml index 3f1b2e96f..391a0804d 100644 --- a/sources/shiboken6/tests/samplebinding/typesystem_sample.xml +++ b/sources/shiboken6/tests/samplebinding/typesystem_sample.xml @@ -120,6 +120,25 @@ </conversion-rule> </primitive-type> + <primitive-type name="std::optional<long>" target-langapi-name="PyObject"> + <conversion-rule> + <native-to-target> + if (!%in.has_value()) + Py_RETURN_NONE; + return PyLong_FromLong(%in.value()); + </native-to-target> + <target-to-native> + <add-conversion type="Py_None"> + SBK_UNUSED(%in) + %out = %OUTTYPE(); + </add-conversion> + <add-conversion type="PyLong" check="PyLong_CheckExact(%in)"> + %out = %OUTTYPE(PyLong_AsLong(%in)); + </add-conversion> + </target-to-native> + </conversion-rule> + </primitive-type> + <value-type name="MoveOnly"/> <object-type name="MoveOnlyHandler"/> |