diff options
Diffstat (limited to 'sources')
57 files changed, 832 insertions, 380 deletions
diff --git a/sources/pyside-tools/pyside_tool.py b/sources/pyside-tools/pyside_tool.py index 887f2bdda..5c009859d 100644 --- a/sources/pyside-tools/pyside_tool.py +++ b/sources/pyside-tools/pyside_tool.py @@ -198,6 +198,18 @@ def metaobjectdump(): pyside_script_wrapper("metaobjectdump.py") +def _check_requirements(requirements_file): + """Check if all required packages are installed.""" + missing_packages = [] + with open(requirements_file, 'r', encoding='UTF-8') as file: + for line in file: + # versions + package = line.strip().split('==')[0] + if not importlib.util.find_spec(package): + missing_packages.append(line.strip()) + return missing_packages + + def project(): pyside_script_wrapper("project.py") @@ -220,12 +232,15 @@ def android_deploy(): file=sys.stderr) else: android_requirements_file = Path(__file__).parent / "requirements-android.txt" - with open(android_requirements_file, 'r', encoding='UTF-8') as file: - while line := file.readline(): - dependent_package = line.rstrip() - if not bool(importlib.util.find_spec(dependent_package)): - command = [sys.executable, "-m", "pip", "install", dependent_package] - subprocess.run(command) + if android_requirements_file.exists(): + missing_packages = _check_requirements(android_requirements_file) + if missing_packages: + print("The following packages are required but not installed:") + for package in missing_packages: + print(f" - {package}") + print("Please install them using:") + print(f" pip install -r {android_requirements_file}") + sys.exit(1) pyside_script_wrapper("android_deploy.py") diff --git a/sources/pyside-tools/requirements-android.txt b/sources/pyside-tools/requirements-android.txt index 9ed5d8427..1a247f6c1 100644 --- a/sources/pyside-tools/requirements-android.txt +++ b/sources/pyside-tools/requirements-android.txt @@ -1,3 +1,4 @@ jinja2 pkginfo tqdm +packaging==24.1 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 207844c56..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> @@ -934,9 +939,12 @@ <inject-code class="target" position="beginning" file="../glue/qtcore.cpp" snippet="qdatetime-2"/> </add-function> <!-- PYSIDE-1735: Qt::TimeSpec is no more compatible with int --> - <add-function signature="QDateTime(int@year@,int@month@,int@day@,int@h@,int@m@,int@s@,int@ms@,Qt::TimeSpec@spec@=Qt::LocalTime)"> + <add-function signature="QDateTime(int@year@,int@month@,int@day@,int@h@,int@m@,int@s@,int@ms@,Qt::TimeSpec@spec@)"> <inject-code class="target" position="beginning" file="../glue/qtcore.cpp" snippet="qdatetime-3"/> </add-function> + <add-function signature="QDateTime(int@year@,int@month@,int@day@,int@h@,int@m@,int@s@,int@ms@,QTimeZone@spec@=QTimeZone::LocalTime)" since="6.5"> + <inject-code class="target" position="beginning" file="../glue/qtcore.cpp" snippet="qdatetime-4"/> + </add-function> <add-function signature="__repr__" return-type="str"> <inject-code class="target" position="beginning"> <insert-template name="repr_code"> diff --git a/sources/pyside6/PySide6/QtWidgets/typesystem_widgets_common.xml b/sources/pyside6/PySide6/QtWidgets/typesystem_widgets_common.xml index a15527c03..c6e93cfd7 100644 --- a/sources/pyside6/PySide6/QtWidgets/typesystem_widgets_common.xml +++ b/sources/pyside6/PySide6/QtWidgets/typesystem_widgets_common.xml @@ -1489,6 +1489,7 @@ <enum-type name="InputMode"/> <modify-function signature="getInt(QWidget*,const QString&,const QString&,int,int,int,int,bool*,QFlags<Qt::WindowType>)" allow-thread="yes"> + <modify-argument index="1" pyi-type="Optional[PySide6.QtWidgets.QWidget]"/> <modify-argument index="return" pyi-type="Tuple[int, bool]"/> <modify-argument index="8"> <remove-default-expression/> @@ -1500,6 +1501,7 @@ </modify-function> <modify-function signature="getItem(QWidget*,const QString&,const QString&,const QStringList&,int,bool,bool*,QFlags<Qt::WindowType>,QFlags<Qt::InputMethodHint>)" allow-thread="yes"> + <modify-argument index="1" pyi-type="Optional[PySide6.QtWidgets.QWidget]"/> <modify-argument index="return" pyi-type="Tuple[str, bool]"/> <modify-argument index="7"> <remove-default-expression/> @@ -1511,6 +1513,7 @@ </modify-function> <modify-function signature="getMultiLineText(QWidget*,const QString&,const QString&,const QString&,bool*,QFlags<Qt::WindowType>,QFlags<Qt::InputMethodHint>)" allow-thread="yes"> + <modify-argument index="1" pyi-type="Optional[PySide6.QtWidgets.QWidget]"/> <modify-argument index="return" pyi-type="Tuple[str, bool]"/> <modify-argument index="5"> <remove-default-expression/> @@ -1522,6 +1525,7 @@ </modify-function> <modify-function signature="getText(QWidget*,const QString&,const QString&,QLineEdit::EchoMode,const QString&,bool*,QFlags<Qt::WindowType>,QFlags<Qt::InputMethodHint>)" allow-thread="yes"> + <modify-argument index="1" pyi-type="Optional[PySide6.QtWidgets.QWidget]"/> <modify-argument index="return" pyi-type="Tuple[str, bool]"/> <modify-argument index="6"> <remove-default-expression/> @@ -1533,6 +1537,7 @@ </modify-function> <modify-function signature="getDouble(QWidget*,const QString&,const QString&,double,double,double,int,bool*,QFlags<Qt::WindowType>,double)" allow-thread="yes"> + <modify-argument index="1" pyi-type="Optional[PySide6.QtWidgets.QWidget]"/> <modify-argument index="return" pyi-type="Tuple[float, bool]"/> <modify-argument index="8"> <remove-default-expression/> 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 78a25f0a1..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) @@ -535,6 +492,12 @@ QTime time(%4, %5, %6, %7); Shiboken::Warnings::warnDeprecated("QDateTime", "QDateTime(..., Qt::TimeSpec spec)"); // @snippet qdatetime-3 +// @snippet qdatetime-4 +QDate date(%1, %2, %3); +QTime time(%4, %5, %6, %7); +%0 = new %TYPE(date, time, QTimeZone(%8)); +// @snippet qdatetime-4 + // @snippet qdatetime-topython QDate date = %CPPSELF.date(); QTime time = %CPPSELF.time(); @@ -1538,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()) { @@ -1560,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 @@ -1575,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/pysidemetafunction.cpp b/sources/pyside6/libpyside/pysidemetafunction.cpp index 48aba3c7b..7a496c4b7 100644 --- a/sources/pyside6/libpyside/pysidemetafunction.cpp +++ b/sources/pyside6/libpyside/pysidemetafunction.cpp @@ -4,6 +4,8 @@ #include "pysidemetafunction.h" #include "pysidemetafunction_p.h" +#include <signalmanager.h> + #include <autodecref.h> #include <basewrapper.h> #include <sbkconverter.h> @@ -12,6 +14,8 @@ #include <QtCore/qmetaobject.h> +using namespace Qt::StringLiterals; + extern "C" { @@ -164,6 +168,10 @@ bool call(QObject *self, int methodIndex, PyObject *args, PyObject **retVal) QString tmp; converter.toCpp(obj, &tmp); methValues[i] = tmp; + } else if (metaType.id() == PyObjectWrapper::metaTypeId()) { + // Manual conversion, see PyObjectWrapper converter registration + methValues[i] = QVariant::fromValue(PyObjectWrapper(obj.object())); + methArgs[i] = methValues[i].data(); } else { converter.toCpp(obj, methArgs[i]); } diff --git a/sources/pyside6/libpyside/pysideproperty.cpp b/sources/pyside6/libpyside/pysideproperty.cpp index e8ea2fa03..69d347043 100644 --- a/sources/pyside6/libpyside/pysideproperty.cpp +++ b/sources/pyside6/libpyside/pysideproperty.cpp @@ -6,6 +6,7 @@ #include "pysideproperty_p.h" #include "pysidesignal.h" #include "pysidesignal_p.h" +#include "signalmanager.h" #include <autodecref.h> #include <pep384ext.h> @@ -17,6 +18,8 @@ using namespace Shiboken; +using namespace Qt::StringLiterals; + extern "C" { @@ -148,16 +151,20 @@ void PySidePropertyPrivate::metaCall(PyObject *source, QMetaObject::Call call, v switch (call) { case QMetaObject::ReadProperty: { AutoDecRef value(getValue(source)); - auto *obValue = value.object(); - if (obValue) { - Conversions::SpecificConverter converter(typeName); - if (converter) { - converter.toCpp(obValue, args[0]); - } else { - // PYSIDE-2160: Report an unknown type name to the caller `qtPropertyMetacall`. - PyErr_SetObject(PyExc_StopIteration, obValue); - } + if (value.isNull()) + return; + if (typeName == "PyObject"_ba) { + // Manual conversion, see PyObjectWrapper converter registration + auto *pw = reinterpret_cast<PySide::PyObjectWrapper *>(args[0]); + pw->reset(value.object()); + return; + } + if (Conversions::SpecificConverter converter(typeName); converter) { + converter.toCpp(value.object(), args[0]); + return; } + // PYSIDE-2160: Report an unknown type name to the caller `qtPropertyMetacall`. + PyErr_SetObject(PyExc_StopIteration, value.object()); } break; 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/libpyside/signalmanager.cpp b/sources/pyside6/libpyside/signalmanager.cpp index 933edd318..305d2f5c3 100644 --- a/sources/pyside6/libpyside/signalmanager.cpp +++ b/sources/pyside6/libpyside/signalmanager.cpp @@ -24,9 +24,12 @@ #include <QtCore/qcoreevent.h> #include <QtCore/qdebug.h> #include <QtCore/qhash.h> +#include <QtCore/qmetatype.h> #include <QtCore/qscopedpointer.h> +#include <climits> #include <memory> +#include <utility> using namespace Qt::StringLiterals; @@ -36,6 +39,8 @@ using namespace Qt::StringLiterals; static PyObject *metaObjectAttr = nullptr; +static int pyObjectWrapperMetaTypeId = QMetaType::UnknownType; + static void destroyMetaObject(PyObject *obj) { void *ptr = PyCapsule_GetPointer(obj, nullptr); @@ -125,6 +130,17 @@ PyObjectWrapper::PyObjectWrapper(const PyObjectWrapper &other) Py_XINCREF(m_me); } +PyObjectWrapper::PyObjectWrapper(PyObjectWrapper &&other) noexcept + : m_me{std::exchange(other.m_me, nullptr)} +{ +} + +PyObjectWrapper &PyObjectWrapper::operator=(PyObjectWrapper &&other) noexcept +{ + m_me = std::exchange(other.m_me, nullptr); + return *this; +} + PyObjectWrapper::~PyObjectWrapper() { // Check that Python is still initialized as sometimes this is called by a static destructor @@ -156,6 +172,10 @@ PyObjectWrapper::operator PyObject *() const return m_me; } +int PyObjectWrapper::metaTypeId() +{ + return pyObjectWrapperMetaTypeId; +} int PyObjectWrapper::toInt() const { @@ -221,7 +241,29 @@ QDataStream &operator>>(QDataStream &in, PyObjectWrapper &myObj) return in; } -}; +PYSIDE_API QDebug operator<<(QDebug debug, const PyObjectWrapper &myObj) +{ + QDebugStateSaver saver(debug); + debug.noquote(); + debug.nospace(); + // Do not repeat the type name as it is typically called from the QVariant debug + // operator, which outputs the type. + debug << '<'; + if (PyObject *ob = myObj) { + const auto refs = Py_REFCNT(ob); + debug << Py_TYPE(ob)->tp_name << " at " << ob; + if (refs == UINT_MAX) // _Py_IMMORTAL_REFCNT + debug << ", immortal"; + else + debug << ", refs=" << refs; + } else { + debug << '0'; + } + debug << '>'; + return debug; +} + +} // namespace PySide using namespace PySide; @@ -238,19 +280,11 @@ struct SignalManagerPrivate SignalManager::QmlMetaCallErrorHandler SignalManagerPrivate::m_qmlMetaCallErrorHandler = nullptr; -static void PyObject_PythonToCpp_PyObject_PTR(PyObject *pyIn, void *cppOut) -{ - *reinterpret_cast<PyObject **>(cppOut) = pyIn; -} -static PythonToCppFunc is_PyObject_PythonToCpp_PyObject_PTR_Convertible(PyObject * /* pyIn */) -{ - return PyObject_PythonToCpp_PyObject_PTR; -} -static PyObject *PyObject_PTR_CppToPython_PyObject(const void *cppIn) +static PyObject *CopyCppToPythonPyObject(const void *cppIn) { - auto *pyOut = reinterpret_cast<PyObject *>(const_cast<void *>(cppIn)); - if (pyOut) - Py_INCREF(pyOut); + const auto *wrapper = reinterpret_cast<const PyObjectWrapper *>(cppIn); + PyObject *pyOut = *wrapper; + Py_XINCREF(pyOut); return pyOut; } @@ -260,13 +294,16 @@ void SignalManager::init() using namespace Shiboken; // Register PyObject type to use in queued signal and slot connections - qRegisterMetaType<PyObjectWrapper>("PyObject"); + pyObjectWrapperMetaTypeId = qRegisterMetaType<PyObjectWrapper>("PyObject"); // Register QVariant(enum) conversion to QVariant(int) QMetaType::registerConverter<PyObjectWrapper, int>(&PyObjectWrapper::toInt); - SbkConverter *converter = Shiboken::Conversions::createConverter(&PyBaseObject_Type, nullptr); - Shiboken::Conversions::setCppPointerToPythonFunction(converter, PyObject_PTR_CppToPython_PyObject); - Shiboken::Conversions::setPythonToCppPointerFunctions(converter, PyObject_PythonToCpp_PyObject_PTR, is_PyObject_PythonToCpp_PyObject_PTR_Convertible); + // Register a shiboken converter for PyObjectWrapper->Python (value conversion). + // Python->PyObjectWrapper is not registered since the converters do not work for + // non-SbkObject types (falling back to plain pointer pass through). + // This conversion needs to be done manually via QVariant. + SbkConverter *converter = Shiboken::Conversions::createConverter(&PyBaseObject_Type, + CopyCppToPythonPyObject); Shiboken::Conversions::registerConverterName(converter, "PyObject"); Shiboken::Conversions::registerConverterName(converter, "object"); Shiboken::Conversions::registerConverterName(converter, "PyObjectWrapper"); diff --git a/sources/pyside6/libpyside/signalmanager.h b/sources/pyside6/libpyside/signalmanager.h index d5c007dbe..9fe56efc2 100644 --- a/sources/pyside6/libpyside/signalmanager.h +++ b/sources/pyside6/libpyside/signalmanager.h @@ -14,6 +14,7 @@ #include <optional> QT_FORWARD_DECLARE_CLASS(QDataStream) +QT_FORWARD_DECLARE_CLASS(QDebug) namespace PySide { @@ -22,13 +23,13 @@ namespace PySide class PYSIDE_API PyObjectWrapper { public: - PyObjectWrapper(PyObjectWrapper&&) = delete; - PyObjectWrapper& operator=(PyObjectWrapper &&) = delete; PyObjectWrapper(); explicit PyObjectWrapper(PyObject* me); PyObjectWrapper(const PyObjectWrapper &other); PyObjectWrapper& operator=(const PyObjectWrapper &other); + PyObjectWrapper(PyObjectWrapper&&) noexcept; + PyObjectWrapper &operator=(PyObjectWrapper &&) noexcept; void reset(PyObject *o); @@ -43,12 +44,15 @@ public: // The proper fix would be to associate PyObjectWrapper to the corresponding C++ Enum. int toInt() const; + static int metaTypeId(); + private: PyObject* m_me; }; PYSIDE_API QDataStream &operator<<(QDataStream& out, const PyObjectWrapper& myObj); PYSIDE_API QDataStream &operator>>(QDataStream& in, PyObjectWrapper& myObj); +PYSIDE_API QDebug operator<<(QDebug debug, const PyObjectWrapper &myObj); class PYSIDE_API SignalManager { diff --git a/sources/pyside6/tests/QtCore/deepcopy_test.py b/sources/pyside6/tests/QtCore/deepcopy_test.py index 8b211a979..64b001e6d 100644 --- a/sources/pyside6/tests/QtCore/deepcopy_test.py +++ b/sources/pyside6/tests/QtCore/deepcopy_test.py @@ -12,8 +12,8 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from init_paths import init_test_paths init_test_paths(False) -from PySide6.QtCore import QByteArray, QDate, QDateTime, QTime, QLine, QLineF -from PySide6.QtCore import Qt, QSize, QSizeF, QRect, QRectF, QDir, QPoint, QPointF +from PySide6.QtCore import QByteArray, QDate, QDateTime, QTime, QLine, QLineF, QTimeZone +from PySide6.QtCore import QSize, QSizeF, QRect, QRectF, QDir, QPoint, QPointF try: from PySide6.QtCore import QUuid HAVE_Q = True @@ -45,7 +45,7 @@ class QTimeDeepCopy(DeepCopyHelper, unittest.TestCase): class QDateTimeDeepCopy(DeepCopyHelper, unittest.TestCase): def setUp(self): - self.original = QDateTime(2010, 5, 18, 10, 24, 45, 223, Qt.TimeSpec.LocalTime) + self.original = QDateTime(2010, 5, 18, 10, 24, 45, 223, QTimeZone(QTimeZone.LocalTime)) class QSizeDeepCopy(DeepCopyHelper, unittest.TestCase): diff --git a/sources/pyside6/tests/QtCore/qobject_property_test.py b/sources/pyside6/tests/QtCore/qobject_property_test.py index 37936205e..80387ec77 100644 --- a/sources/pyside6/tests/QtCore/qobject_property_test.py +++ b/sources/pyside6/tests/QtCore/qobject_property_test.py @@ -32,6 +32,26 @@ class MyObjectWithNotifyProperty(QObject): myProperty = Property(int, readP, fset=writeP, notify=notifyP) +class OtherClass: + """Helper for QObjectWithOtherClassPropertyTest.""" + pass + + +class MyObjectWithOtherClassProperty(QObject): + """Helper for QObjectWithOtherClassPropertyTest.""" + def __init__(self, parent=None): + super().__init__(parent) + self._otherclass = None + + def _get_otherclass(self): + return self._otherclass + + def _set_otherclass(self, o): + self._otherclass = o + + otherclass = Property(OtherClass, fget=_get_otherclass, fset=_set_otherclass) + + class PropertyWithNotify(unittest.TestCase): def called(self): self.called_ = True @@ -50,5 +70,19 @@ class PropertyWithNotify(unittest.TestCase): self.assertEqual(o.property("myProperty"), 10) +class QObjectWithOtherClassPropertyTest(unittest.TestCase): + """PYSIDE-2193: For properties of custom classes not wrapped by shiboken, + QVariant<PyObjectWrapper> is used, which had refcount issues causing crashes. + Exercise the QVariant conversion by setting and retrieving via the + QVariant-based property()/setProperty() API.""" + def testNotify(self): + obj = MyObjectWithOtherClassProperty() + obj.setProperty("otherclass", OtherClass()) + for i in range(10): + pv = obj.property("otherclass") + print(pv) # Exercise repr + self.assertTrue(type(pv) is OtherClass) + + if __name__ == '__main__': unittest.main() diff --git a/sources/pyside6/tests/QtCore/repr_test.py b/sources/pyside6/tests/QtCore/repr_test.py index 084b69f87..52a426d31 100644 --- a/sources/pyside6/tests/QtCore/repr_test.py +++ b/sources/pyside6/tests/QtCore/repr_test.py @@ -13,8 +13,8 @@ init_test_paths(False) # for 'self.original' import PySide6 # noqa -from PySide6.QtCore import QByteArray, QDate, QDateTime, QTime, QLine, QLineF -from PySide6.QtCore import Qt, QSize, QSizeF, QRect, QRectF, QPoint, QPointF +from PySide6.QtCore import QByteArray, QDate, QDateTime, QTime, QLine, QLineF, QTimeZone +from PySide6.QtCore import QSize, QSizeF, QRect, QRectF, QPoint, QPointF try: from PySide6.QtCore import QUuid HAVE_Q = True @@ -46,7 +46,7 @@ class QTimeReprCopy(ReprCopyHelper, unittest.TestCase): class QDateTimeReprCopy(ReprCopyHelper, unittest.TestCase): def setUp(self): - self.original = QDateTime(2010, 5, 18, 10, 24, 45, 223, Qt.TimeSpec.LocalTime) + self.original = QDateTime(2010, 5, 18, 10, 24, 45, 223, QTimeZone(QTimeZone.LocalTime)) class QSizeReprCopy(ReprCopyHelper, unittest.TestCase): diff --git a/sources/pyside6/tests/QtWidgets/bug_433.py b/sources/pyside6/tests/QtWidgets/bug_433.py index 5adcacccc..6d5c4333a 100644 --- a/sources/pyside6/tests/QtWidgets/bug_433.py +++ b/sources/pyside6/tests/QtWidgets/bug_433.py @@ -26,4 +26,4 @@ a = QApplication(sys.argv) t = Test() t.show() QTimer.singleShot(0, t.close) -sys.exit(a.exec_()) +sys.exit(a.exec()) diff --git a/sources/pyside6/tests/QtWidgets/bug_919.py b/sources/pyside6/tests/QtWidgets/bug_919.py index 6f2fc128d..a95d3aa37 100644 --- a/sources/pyside6/tests/QtWidgets/bug_919.py +++ b/sources/pyside6/tests/QtWidgets/bug_919.py @@ -26,10 +26,11 @@ class MyWidget(QPushButton): self.paintReceived.emit() def paintEvent(self, e): - p = QPainter(self) - style = QApplication.style() - option = QStyleOptionButton() - style.drawControl(QStyle.ControlElement.CE_PushButton, option, p) + with QPainter(self) as p: + style = QApplication.style() + option = QStyleOptionButton() + self.initStyleOption(option) + style.drawControl(QStyle.ControlElement.CE_PushButton, option, p) self._painted = True QTimer.singleShot(0, self._emitPainted) diff --git a/sources/pyside6/tests/QtWidgets/qapp_issue_585.py b/sources/pyside6/tests/QtWidgets/qapp_issue_585.py index ec8a47569..1300ea4aa 100644 --- a/sources/pyside6/tests/QtWidgets/qapp_issue_585.py +++ b/sources/pyside6/tests/QtWidgets/qapp_issue_585.py @@ -40,15 +40,15 @@ import sys from pathlib import Path sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) sys.path.append(os.fspath(Path(__file__).resolve().parents[1] / "util")) -from init_paths import init_test_paths +from init_paths import init_test_paths # noqa: E402 init_test_paths() -from PySide6.QtCore import QTimer -from PySide6.QtWidgets import QApplication +from PySide6.QtCore import QTimer # noqa: E402 +from PySide6.QtWidgets import QApplication # noqa: E402 app_instance = QApplication([]) # If the following line is commented, application doesn't crash on exit anymore. app_instance2 = app_instance -QTimer.singleShot(0, qApp.quit) -app_instance.exec_() +QTimer.singleShot(0, qApp.quit) # noqa: F821 +app_instance.exec() diff --git a/sources/pyside6/tests/QtWidgets/qmenu_test.py b/sources/pyside6/tests/QtWidgets/qmenu_test.py index 7d1d262e4..8bd5d1624 100644 --- a/sources/pyside6/tests/QtWidgets/qmenu_test.py +++ b/sources/pyside6/tests/QtWidgets/qmenu_test.py @@ -33,16 +33,16 @@ class QMenuAddAction(UsesQApplication): def testAddActionWithoutKeySequenceCallable(self): # bug #280 - action = self.menu.addAction(self.app.tr('aaa'), lambda: 1) + action = self.menu.addAction(self.app.tr('aaa'), lambda: 1) # noqa: F841 def testAddActionKeySequenceCallable(self): # bug #228 - action = self.menu.addAction(self.app.tr('aaa'), lambda: 1, + action = self.menu.addAction(self.app.tr('aaa'), lambda: 1, # noqa: F841 QKeySequence(self.app.tr('Ctrl+O'))) def testAddActionKeySequenceSlot(self): - action = self.menu.addAction('Quit', self.app, SLOT('quit()'), - QKeySequence('Ctrl+O')) + action = self.menu.addAction('Quit', QKeySequence('Ctrl+O'), # noqa: F841 + self.app, SLOT('quit()')) class QMenuAddActionWithIcon(UsesQApplication): @@ -61,16 +61,16 @@ class QMenuAddActionWithIcon(UsesQApplication): def testAddActionWithoutKeySequenceCallable(self): # bug #280 - action = self.menu.addAction(self.icon, self.app.tr('aaa'), lambda: 1) + action = self.menu.addAction(self.icon, self.app.tr('aaa'), lambda: 1) # noqa: F841 def testAddActionKeySequenceCallable(self): # bug #228 - action = self.menu.addAction(self.icon, self.app.tr('aaa'), lambda: 1, - QKeySequence(self.app.tr('Ctrl+O'))) + action = self.menu.addAction(self.icon, self.app.tr('aaa'), lambda: 1, # noqa: F841 + QKeySequence(self.app.tr('Ctrl+O'))) # noqa: F841 def testAddActionKeySequenceSlot(self): - action = self.menu.addAction(self.icon, 'Quit', self.app, SLOT('quit()'), - QKeySequence('Ctrl+O')) + action = self.menu.addAction(self.icon, 'Quit', QKeySequence('Ctrl+O'), # noqa: F841 + self.app, SLOT('quit()')) # noqa: F841 if __name__ == '__main__': 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/pyside6/tests/tools/pyside6-android-deploy/test_pyside6_android_deploy.py b/sources/pyside6/tests/tools/pyside6-android-deploy/test_pyside6_android_deploy.py index 120c54af3..14330726a 100644 --- a/sources/pyside6/tests/tools/pyside6-android-deploy/test_pyside6_android_deploy.py +++ b/sources/pyside6/tests/tools/pyside6-android-deploy/test_pyside6_android_deploy.py @@ -36,7 +36,7 @@ class DeployTestBase(unittest.TestCase): android_requirements_file = pyside_tools / "requirements-android.txt" with open(android_requirements_file, 'r', encoding='UTF-8') as file: while line := file.readline(): - dependent_package = line.rstrip() + dependent_package = line.rstrip().split('==')[0] if not bool(importlib.util.find_spec(dependent_package)): command = [sys.executable, "-m", "pip", "install", dependent_package] subprocess.run(command) 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/apiextractor.cpp b/sources/shiboken6/ApiExtractor/apiextractor.cpp index 90f4f5dd9..38dedfd14 100644 --- a/sources/shiboken6/ApiExtractor/apiextractor.cpp +++ b/sources/shiboken6/ApiExtractor/apiextractor.cpp @@ -370,6 +370,7 @@ bool ApiExtractorPrivate::runHelper(ApiExtractorFlags flags) bool addCompilerSupportArguments = true; if (clangOptionsSize > 0) { + clang::setTargetTriple(m_clangOptions); qsizetype i = 0; if (m_clangOptions.at(i) == u"-") { ++i; diff --git a/sources/shiboken6/ApiExtractor/clangparser/clangparser.cpp b/sources/shiboken6/ApiExtractor/clangparser/clangparser.cpp index 54a1a2c8b..747937ede 100644 --- a/sources/shiboken6/ApiExtractor/clangparser/clangparser.cpp +++ b/sources/shiboken6/ApiExtractor/clangparser/clangparser.cpp @@ -275,6 +275,24 @@ static CXTranslationUnit createTranslationUnit(CXIndex index, return tu; } +static void setupTarget(CXTranslationUnit translationUnit) +{ + const CXTargetInfo targetInfo = clang_getTranslationUnitTargetInfo(translationUnit); + const auto tripleCS = clang_TargetInfo_getTriple(targetInfo); + clang::setPointerSize(clang_TargetInfo_getPointerWidth(targetInfo)); + clang::setTargetTriple(QString::fromUtf8(clang_getCString(tripleCS))); + clang_disposeString(tripleCS); + + QString message; + { + QTextStream str(&message); + str << "CLANG v" << CINDEX_VERSION_MAJOR << '.' << CINDEX_VERSION_MINOR + << " targeting \"" << targetTriple() << "\", " << pointerSize() << "bit."; + } + qCInfo(lcShiboken, "%s", qPrintable(message)); + ReportHandler::addGeneralMessage(message + u'\n'); +} + /* clangFlags are flags to clang_parseTranslationUnit2() such as * CXTranslationUnit_KeepGoing (from CINDEX_VERSION_MAJOR/CINDEX_VERSION_MINOR 0.35) */ @@ -295,6 +313,8 @@ bool parse(const QByteArrayList &clangArgs, bool addCompilerSupportArguments, if (!translationUnit) return false; + setupTarget(translationUnit); + CXCursor rootCursor = clang_getTranslationUnitCursor(translationUnit); clang_visitChildren(rootCursor, visitorCallback, reinterpret_cast<CXClientData>(&bv)); diff --git a/sources/shiboken6/ApiExtractor/clangparser/compilersupport.cpp b/sources/shiboken6/ApiExtractor/clangparser/compilersupport.cpp index ed8057b62..4d93a084f 100644 --- a/sources/shiboken6/ApiExtractor/clangparser/compilersupport.cpp +++ b/sources/shiboken6/ApiExtractor/clangparser/compilersupport.cpp @@ -59,6 +59,9 @@ bool setCompiler(const QString &name) QString _compilerPath; // Pre-defined compiler path (from command line) +static unsigned _pointerSize = QT_POINTER_SIZE * 8; +static QString _targetTriple; + const QString &compilerPath() { return _compilerPath; @@ -153,14 +156,15 @@ static void filterHomebrewHeaderPaths(HeaderPaths &headerPaths) if (homebrewPrefix.isEmpty()) return; - qCInfo(lcShiboken) << "Found HOMEBREW_OPT with value:" << homebrewPrefix - << "Assuming homebrew build environment."; + ReportHandler::addGeneralMessage("Found HOMEBREW_OPT with value:"_L1 + + QString::fromUtf8(homebrewPrefix) + + "\nAssuming homebrew build environment."_L1); HeaderPaths::iterator it = headerPaths.begin(); while (it != headerPaths.end()) { if (it->path.startsWith(homebrewPrefix)) { - qCInfo(lcShiboken) << "Filtering out homebrew include path: " - << it->path; + ReportHandler::addGeneralMessage("Filtering out homebrew include path: "_L1 + + QString::fromUtf8(it->path)); it = headerPaths.erase(it); } else { ++it; @@ -186,12 +190,6 @@ static HeaderPaths gppInternalIncludePaths(const QString &compiler) const QByteArrayList stdErrLines = stdErr.split('\n'); bool isIncludeDir = false; - if (ReportHandler::isDebug(ReportHandler::MediumDebug)) - qCInfo(lcShiboken()).noquote().nospace() - << "gppInternalIncludePaths:\n compiler: " << compiler - << "\n stdOut: " << stdOut - << "\n stdErr: " << stdErr; - for (const QByteArray &line : stdErrLines) { if (isIncludeDir) { if (line.startsWith(QByteArrayLiteral("End of search list"))) { @@ -212,6 +210,17 @@ static HeaderPaths gppInternalIncludePaths(const QString &compiler) if (platform() == Platform::macOS) filterHomebrewHeaderPaths(result); + QString message; + { + QTextStream str(&message); + str << "gppInternalIncludePaths:\n compiler: " << compiler << '\n'; + for (const auto &h : result) + str << " " << h.path << '\n'; + if (ReportHandler::isDebug(ReportHandler::MediumDebug)) + str << " stdOut: " << stdOut << "\n stdErr: " << stdErr; + } + ReportHandler::addGeneralMessage(message); + return result; } @@ -365,11 +374,10 @@ static void appendClangBuiltinIncludes(HeaderPaths *p) "(neither by checking the environment variables LLVM_INSTALL_DIR, CLANG_INSTALL_DIR " " nor running llvm-config). This may lead to parse errors."); } else { - qCInfo(lcShiboken, "CLANG v%d.%d, builtins includes directory: %s", - CINDEX_VERSION_MAJOR, CINDEX_VERSION_MINOR, - qPrintable(clangBuiltinIncludesDir)); p->append(HeaderPath{QFile::encodeName(clangBuiltinIncludesDir), HeaderType::System}); + ReportHandler::addGeneralMessage("CLANG builtins includes directory: "_L1 + + clangBuiltinIncludesDir); } } @@ -451,4 +459,33 @@ LanguageLevel languageLevelFromOption(const char *o) return LanguageLevel::Default; } +unsigned pointerSize() +{ + return _pointerSize; +} + +void setPointerSize(unsigned ps) +{ + _pointerSize = ps; +} + +QString targetTriple() +{ + return _targetTriple; + +} +void setTargetTriple(const QString &t) +{ + _targetTriple = t; +} + +void setTargetTriple(const QStringList &clangOptions) +{ + static constexpr auto targetOption = "--target="_L1; + auto targetOptionPred = [](const QString &o) { return o.startsWith(targetOption); }; + const auto it = std::find_if(clangOptions.cbegin(), clangOptions.cend(), targetOptionPred); + if (it != clangOptions.cend()) + _targetTriple = it->sliced(targetOption.size()); +} + } // namespace clang diff --git a/sources/shiboken6/ApiExtractor/clangparser/compilersupport.h b/sources/shiboken6/ApiExtractor/clangparser/compilersupport.h index 1fa980998..0e12ca137 100644 --- a/sources/shiboken6/ApiExtractor/clangparser/compilersupport.h +++ b/sources/shiboken6/ApiExtractor/clangparser/compilersupport.h @@ -51,6 +51,14 @@ void setCompilerPath(const QString &name); Platform platform(); bool setPlatform(const QString &name); + +unsigned pointerSize(); // (bit) +void setPointerSize(unsigned ps); // Set by parser + +QString targetTriple(); +void setTargetTriple(const QStringList &clangOptions); // Set from cmd line before parsing +void setTargetTriple(const QString &t); // Updated by clang parser while parsing + } // namespace clang #endif // COMPILERSUPPORT_H diff --git a/sources/shiboken6/ApiExtractor/customconversion.cpp b/sources/shiboken6/ApiExtractor/customconversion.cpp index 4cfd1b974..7b65bcf3a 100644 --- a/sources/shiboken6/ApiExtractor/customconversion.cpp +++ b/sources/shiboken6/ApiExtractor/customconversion.cpp @@ -5,6 +5,7 @@ #include "containertypeentry.h" #include "customtypenentry.h" #include "primitivetypeentry.h" +#include "smartpointertypeentry.h" #include "valuetypeentry.h" #include <QtCore/qdebug.h> @@ -139,6 +140,8 @@ CustomConversionPtr CustomConversion::getCustomConversion(const TypeEntryCPtr &t return std::static_pointer_cast<const ContainerTypeEntry>(type)->customConversion(); if (type->isValue()) return std::static_pointer_cast<const ValueTypeEntry>(type)->customConversion(); + if (type->isSmartPointer()) + return std::static_pointer_cast<const SmartPointerTypeEntry>(type)->customConversion(); return {}; } 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/ApiExtractor/smartpointertypeentry.h b/sources/shiboken6/ApiExtractor/smartpointertypeentry.h index d704210f7..7b67647b9 100644 --- a/sources/shiboken6/ApiExtractor/smartpointertypeentry.h +++ b/sources/shiboken6/ApiExtractor/smartpointertypeentry.h @@ -5,6 +5,7 @@ #define SMARTPOINTERTYPEENTRY_H #include "complextypeentry.h" +#include "customconversion_typedefs.h" class SmartPointerTypeEntryPrivate; @@ -51,6 +52,10 @@ public: QString getTargetName(const AbstractMetaType &metaType) const; + bool hasCustomConversion() const; + void setCustomConversion(const CustomConversionPtr &customConversion); + CustomConversionPtr customConversion() const; + #ifndef QT_NO_DEBUG_STREAM void formatDebug(QDebug &d) const override; #endif diff --git a/sources/shiboken6/ApiExtractor/typesystem.cpp b/sources/shiboken6/ApiExtractor/typesystem.cpp index 0820150f8..46e88291d 100644 --- a/sources/shiboken6/ApiExtractor/typesystem.cpp +++ b/sources/shiboken6/ApiExtractor/typesystem.cpp @@ -2154,6 +2154,7 @@ public: QString m_resetMethod; SmartPointerTypeEntry::Instantiations m_instantiations; TypeEntryCList m_excludedInstantiations; + CustomConversionPtr m_customConversion; TypeSystem::SmartPointerType m_smartPointerType; }; @@ -2306,6 +2307,24 @@ QString SmartPointerTypeEntry::getTargetName(const AbstractMetaType &metaType) c return fixSmartPointerName(name); } +bool SmartPointerTypeEntry::hasCustomConversion() const +{ + S_D(const SmartPointerTypeEntry); + return bool(d->m_customConversion); +} + +void SmartPointerTypeEntry::setCustomConversion(const CustomConversionPtr &customConversion) +{ + S_D(SmartPointerTypeEntry); + d->m_customConversion = customConversion; +} + +CustomConversionPtr SmartPointerTypeEntry::customConversion() const +{ + S_D(const SmartPointerTypeEntry); + return d->m_customConversion; +} + // ----------------- NamespaceTypeEntry class NamespaceTypeEntryPrivate : public ComplexTypeEntryPrivate { diff --git a/sources/shiboken6/ApiExtractor/typesystemparser.cpp b/sources/shiboken6/ApiExtractor/typesystemparser.cpp index 8b2ad08de..7ae25e48c 100644 --- a/sources/shiboken6/ApiExtractor/typesystemparser.cpp +++ b/sources/shiboken6/ApiExtractor/typesystemparser.cpp @@ -2348,9 +2348,10 @@ bool TypeSystemParser::parseCustomConversion(const ConditionalStreamReader &, if (topElement != StackElement::ModifyArgument && topElement != StackElement::ValueTypeEntry && topElement != StackElement::PrimitiveTypeEntry - && topElement != StackElement::ContainerTypeEntry) { + && topElement != StackElement::ContainerTypeEntry + && topElement != StackElement::SmartPointerTypeEntry) { m_error = u"Conversion rules can only be specified for argument modification, " - "value-type, primitive-type or container-type conversion."_s; + "value-type, primitive-type, or container-type or smartpointer-type conversion."_s; return false; } @@ -2415,6 +2416,9 @@ bool TypeSystemParser::parseCustomConversion(const ConditionalStreamReader &, std::static_pointer_cast<ContainerTypeEntry>(top->entry)->setCustomConversion(customConversion); else if (top->entry->isValue()) std::static_pointer_cast<ValueTypeEntry>(top->entry)->setCustomConversion(customConversion); + else if (top->entry->isSmartPointer()) + std::static_pointer_cast<SmartPointerTypeEntry>(top->entry)->setCustomConversion(customConversion); + customConversionsForReview.append(customConversion); return true; } diff --git a/sources/shiboken6/cmake/ShibokenHelpers.cmake b/sources/shiboken6/cmake/ShibokenHelpers.cmake index bcaf31e89..c6414b2ca 100644 --- a/sources/shiboken6/cmake/ShibokenHelpers.cmake +++ b/sources/shiboken6/cmake/ShibokenHelpers.cmake @@ -869,28 +869,9 @@ endfunction() # Get path to libclang.dll/libclang.so depending on the platform macro(find_libclang) - if(CMAKE_HOST_WIN32) - set(libclang_directory_suffix "bin") - set(libclang_suffix ".dll") - else() - set(libclang_directory_suffix "lib") - if(CMAKE_HOST_APPLE) - set(libclang_suffix ".dylib") - else() - set(libclang_suffix ".so") - endif() - endif() - - set(libclang_lib_dir "") - if(DEFINED ENV{LLVM_INSTALL_DIR}) - set(libclang_lib_dir "$ENV{LLVM_INSTALL_DIR}/${libclang_directory_suffix}") - elseif(DEFINED ENV{CLANG_INSTALL_DIR}) - set(libclang_lib_dir "$ENV{CLANG_INSTALL_DIR}/${libclang_directory_suffix}") - else() - message(WARNING - "Couldn't find libclang${libclang_suffix} " - "You will likely need to add it manually to PATH to ensure the build succeeds.") - endif() + find_package(Clang CONFIG REQUIRED) + get_target_property(libclang_location libclang LOCATION) + get_filename_component(libclang_lib_dir "${libclang_location}" DIRECTORY) endmacro() # Allow setting a shiboken debug level from the the build system or from the environment diff --git a/sources/shiboken6/doc/typesystem_conversionrule.rst b/sources/shiboken6/doc/typesystem_conversionrule.rst index cee45bfb3..f6ce18345 100644 --- a/sources/shiboken6/doc/typesystem_conversionrule.rst +++ b/sources/shiboken6/doc/typesystem_conversionrule.rst @@ -12,9 +12,13 @@ The **conversion-rule** tag specifies how a **primitive-type**, a **container-ty or a **value-type** may be converted to and from the native C++ language types to the target language types (see also :ref:`user-defined-type-conversion`). -It is a child of the :ref:`container-type`, :ref:`primitive-type` or -:ref:`value-type` and may contain :ref:`native-to-target` or -:ref:`native-to-target` child nodes. +It may be a child of the :ref:`container-type` and :ref:`primitive-type` nodes, +where conversions have to be provided for both directions using the +:ref:`native-to-target` and :ref:`target-to-native` child nodes. + +It may also appear as a child of :ref:`value-type` or :ref:`smart-pointer-type` +where additional conversions from other target language types can be provided +using the :ref:`target-to-native` child node. .. code-block:: xml @@ -70,8 +74,9 @@ an input value an does what's needed to convert it to the output value. </conversion-rule> Use the replace node to modify the template code. -Notice that the generator must provide type system variables for the input -and output values and types, namely **%in**, **%out**, **%INTYPE** and +Notice that the generator provides type system variables for the input +and output values and types (see :ref:`converter_variables_and_functions`). +The most important ones are **%in**, **%out**, **%INTYPE** and **%OUTTYPE**. In the case of container types, **%INTYPE** refers to the full container type (e.g. **"list<int>"**) and **%INTYPE_0**, **%INTYPE_1**, **%INTYPE_#**, should be replaced by the types used in the container template 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..798ef5719 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. @@ -726,7 +729,8 @@ The ``smart pointer`` type node indicates that the given class is a smart pointe and requires inserting calls to **getter** to access the pointeee. Currently, the usage is limited to function return values. **ref-count-method** specifies the name of the method used to do reference counting. -It is a child of the :ref:`typesystem_details` node or other type nodes. +It is a child of the :ref:`typesystem_details` node or other type nodes +and may contain :ref:`conversion-rule` nodes. The *optional* attribute **instantiations** specifies for which instantiations of the smart pointer wrappers will be generated (comma-separated list). 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/generator/shiboken/cppgenerator.cpp b/sources/shiboken6/generator/shiboken/cppgenerator.cpp index 21e167b53..9092768a3 100644 --- a/sources/shiboken6/generator/shiboken/cppgenerator.cpp +++ b/sources/shiboken6/generator/shiboken/cppgenerator.cpp @@ -997,7 +997,7 @@ void CppGenerator::writeCacheResetNative(TextStream &s, const GeneratorContext & { s << "void " << classContext.wrapperName() << "::resetPyMethodCache()\n{\n" << indent - << "std::fill_n(m_PyMethodCache, sizeof(m_PyMethodCache) / sizeof(m_PyMethodCache[0]), false);\n" + << "std::fill(m_PyMethodCache.begin(), m_PyMethodCache.end(), nullptr);\n" << outdent << "}\n\n"; } @@ -2120,6 +2120,24 @@ void CppGenerator::writeCustomConverterRegister(TextStream &s, } } +void CppGenerator::writeTemplateCustomConverterRegister(TextStream &s, + const AbstractMetaType &type, + QString converter) +{ + auto customConversion = CustomConversion::getCustomConversion(type.typeEntry()); + if (!customConversion || customConversion->targetToNativeConversions().isEmpty()) + return; + if (converter.isEmpty()) + converter = converterVar; + const QString typeName = fixedCppTypeName(type); + for (const auto &conv : customConversion->targetToNativeConversions()) { + const QString &sourceTypeName = conv.sourceTypeName(); + QString toCpp = pythonToCppFunctionName(sourceTypeName, typeName); + QString isConv = convertibleToCppFunctionName(sourceTypeName, typeName); + writeAddPythonToCppConversion(s, converter, toCpp, isConv); + } +} + void CppGenerator::writeContainerConverterFunctions(TextStream &s, const AbstractMetaType &containerType) const { @@ -3600,13 +3618,15 @@ void CppGenerator::writePythonToCppConversionFunctions(TextStream &s, writeIsPythonConvertibleToCppFunction(s, sourceTypeName, targetTypeName, typeCheck); } -void CppGenerator::writePythonToCppConversionFunctions(TextStream &s, const AbstractMetaType &containerType) const +void CppGenerator::writePythonToCppConversionFunctions(TextStream &s, + const AbstractMetaType &templateType) const { - Q_ASSERT(containerType.typeEntry()->isContainer()); - const auto cte = std::static_pointer_cast<const ContainerTypeEntry>(containerType.typeEntry()); - const auto customConversion = cte->customConversion(); - for (const auto &conv : customConversion->targetToNativeConversions()) - writePythonToCppConversionFunction(s, containerType, conv); + const auto customConversion = CustomConversion::getCustomConversion(templateType.typeEntry()); + if (customConversion) { + const auto &conversions = customConversion->targetToNativeConversions(); + for (const auto &conv : conversions) + writePythonToCppConversionFunction(s, templateType, conv); + } } void CppGenerator::writePythonToCppConversionFunction(TextStream &s, @@ -4438,8 +4458,7 @@ QString CppGenerator::writeContainerConverterInitialization(TextStream &s, s << '&' << targetTypeName << "_Type"; } - const QString typeName = fixedCppTypeName(type); - s << ", " << cppToPythonFunctionName(typeName, targetTypeName) << ");\n"; + s << ", " << cppToPythonFunctionName(fixedCppTypeName(type), targetTypeName) << ");\n"; s << registerConverterName(cppSignature, converter); if (usePySideExtensions() && cppSignature.startsWith("const "_L1) @@ -4448,12 +4467,7 @@ QString CppGenerator::writeContainerConverterInitialization(TextStream &s, s << registerConverterName(underlyingType, converter); } - for (const auto &conv : typeEntry->customConversion()->targetToNativeConversions()) { - const QString &sourceTypeName = conv.sourceTypeName(); - QString toCpp = pythonToCppFunctionName(sourceTypeName, typeName); - QString isConv = convertibleToCppFunctionName(sourceTypeName, typeName); - writeAddPythonToCppConversion(s, converter, toCpp, isConv); - } + writeTemplateCustomConverterRegister(s, type, converter); auto typedefItPair = api.typedefTargetToName().equal_range(type.cppSignature()); if (typedefItPair.first != typedefItPair.second) { diff --git a/sources/shiboken6/generator/shiboken/cppgenerator.h b/sources/shiboken6/generator/shiboken/cppgenerator.h index 0cf204234..441a9f781 100644 --- a/sources/shiboken6/generator/shiboken/cppgenerator.h +++ b/sources/shiboken6/generator/shiboken/cppgenerator.h @@ -114,6 +114,9 @@ private: static void writeCustomConverterRegister(TextStream &s, const CustomConversionPtr &customConversion, const QString &converterVar); + static void writeTemplateCustomConverterRegister(TextStream &s, + const AbstractMetaType &type, + QString converter = {}); void writeContainerConverterFunctions(TextStream &s, const AbstractMetaType &containerType) const; @@ -361,9 +364,10 @@ private: const TargetToNativeConversion &toNative, const TypeEntryCPtr &targetType) const; - /// Writes a pair of Python to C++ conversion and check functions for instantiated container types. + /// Writes a pair of Python to C++ conversion and check functions for instantiated + /// template (smart pointer/container types). void writePythonToCppConversionFunctions(TextStream &s, - const AbstractMetaType &containerType) const; + const AbstractMetaType &templateType) const; void writePythonToCppConversionFunction(TextStream &s, const AbstractMetaType &containerType, diff --git a/sources/shiboken6/generator/shiboken/cppgenerator_smartpointer.cpp b/sources/shiboken6/generator/shiboken/cppgenerator_smartpointer.cpp index 48c4ed5d2..1d3280e36 100644 --- a/sources/shiboken6/generator/shiboken/cppgenerator_smartpointer.cpp +++ b/sources/shiboken6/generator/shiboken/cppgenerator_smartpointer.cpp @@ -232,13 +232,16 @@ void CppGenerator::generateSmartPointerClass(TextStream &s, void CppGenerator::writeSmartPointerConverterFunctions(TextStream &s, const AbstractMetaType &smartPointerType) const { + auto smartPointerTypeEntry = + std::static_pointer_cast<const SmartPointerTypeEntry>(smartPointerType.typeEntry()); + + if (smartPointerTypeEntry->hasCustomConversion()) + writePythonToCppConversionFunctions(s, smartPointerType); + const auto baseClasses = findSmartPointeeBaseClasses(api(), smartPointerType); if (baseClasses.isEmpty()) return; - auto smartPointerTypeEntry = - std::static_pointer_cast<const SmartPointerTypeEntry>(smartPointerType.typeEntry()); - // TODO: Missing conversion to smart pointer pointer type: s << "// Register smartpointer conversion for all derived classes\n"; @@ -290,6 +293,8 @@ void CppGenerator::writeSmartPointerConverterInitialization(TextStream &s, writeAddPythonToCppConversion(s, targetConverter, toCpp, isConv); }; + writeTemplateCustomConverterRegister(s, type); + const auto classes = findSmartPointeeBaseClasses(api(), type); if (classes.isEmpty()) return; diff --git a/sources/shiboken6/generator/shiboken/headergenerator.cpp b/sources/shiboken6/generator/shiboken/headergenerator.cpp index 965d80dad..7616fa5c3 100644 --- a/sources/shiboken6/generator/shiboken/headergenerator.cpp +++ b/sources/shiboken6/generator/shiboken/headergenerator.cpp @@ -185,6 +185,7 @@ void HeaderGenerator::writeWrapperClass(TextStream &s, for( const auto &includeGroup : includeGroups) s << includeGroup; } + s << "#include <sbkpython.h>\n\n#include <array>\n"; s << "namespace Shiboken { class AutoDecRef; class GilState; }\n\n"; @@ -288,9 +289,10 @@ void *qt_metacast(const char *_clname) override; } if (needsMethodCache) { - s << "mutable bool m_PyMethodCache[" << maxOverrides << "] = {false"; + s << "mutable std::array<PyObject *, " << maxOverrides + << "> m_PyMethodCache = {nullptr"; for (int i = 1; i < maxOverrides; ++i) - s << ", false"; + s << ", nullptr"; s << "};\n"; } diff --git a/sources/shiboken6/libshiboken/basewrapper.cpp b/sources/shiboken6/libshiboken/basewrapper.cpp index 3e26b4605..039cd3085 100644 --- a/sources/shiboken6/libshiboken/basewrapper.cpp +++ b/sources/shiboken6/libshiboken/basewrapper.cpp @@ -21,6 +21,7 @@ #include "voidptr.h" #include <algorithm> +#include <cctype> #include <cstddef> #include <cstring> #include <iostream> @@ -786,32 +787,57 @@ static PyObject *overrideMethodName(PyObject *pySelf, const char *methodName, // The virtual function call PyObject *Sbk_GetPyOverride(const void *voidThis, PyTypeObject *typeObject, Shiboken::GilState &gil, const char *funcName, - bool &resultCache, PyObject **nameCache) -{ - PyObject *pyOverride{}; - if (!resultCache) { - gil.acquire(); - auto &bindingManager = Shiboken::BindingManager::instance(); - SbkObject *wrapper = bindingManager.retrieveWrapper(voidThis, typeObject); - // The refcount can be 0 if the object is dieing and someone called - // a virtual method from the destructor - if (wrapper == nullptr) - return nullptr; - auto *pySelf = reinterpret_cast<PyObject *>(wrapper); - if (Py_REFCNT(pySelf) == 0) - return nullptr; - PyObject *pyMethodName = overrideMethodName(pySelf, funcName, nameCache); - pyOverride = Shiboken::BindingManager::getOverride(wrapper, pyMethodName); - if (pyOverride == nullptr) { - resultCache = true; - gil.release(); - } else if (Shiboken::Errors::occurred() != nullptr) { - // Give up. - Py_XDECREF(pyOverride); - pyOverride = nullptr; - } + PyObject *&resultCache, PyObject **nameCache) +{ + if (Py_IsInitialized() == 0 || resultCache == Py_None) + return nullptr; // Bail out, execute C++ call (wrappers may outlive Python). + + auto &bindingManager = Shiboken::BindingManager::instance(); + SbkObject *wrapper = bindingManager.retrieveWrapper(voidThis, typeObject); + // The refcount can be 0 if the object is dieing and someone called + // a virtual method from the destructor + if (wrapper == nullptr) + return nullptr; + auto *pySelf = reinterpret_cast<PyObject *>(wrapper); + if (Py_REFCNT(pySelf) == 0) + return nullptr; + + gil.acquire(); + + if (resultCache != nullptr) // recreate the callable from function/self + return PepExt_Type_CallDescrGet(resultCache, pySelf, nullptr); + + PyObject *pyMethodName = overrideMethodName(pySelf, funcName, nameCache); + auto *wrapper_dict = SbkObject_GetDict_NoRef(pySelf); + + // Note: This special case was implemented for duck-punching, which happens + // in the instance dict. It does not work with properties. + // This is not cached to avoid leaking. FIXME PYSIDE 7: Remove (PYSIDE-2916)? + if (PyObject *method = PyDict_GetItem(wrapper_dict, pyMethodName)) { + Py_INCREF(method); + return method; + } + + auto *pyOverride = Shiboken::BindingManager::getOverride(wrapper, pyMethodName); + if (pyOverride == nullptr) { + resultCache = Py_None; + Py_INCREF(resultCache); + gil.release(); + return nullptr; // No override, execute C++ call } - return pyOverride; + + if (Shiboken::Errors::occurred() != nullptr) { + // Give up. + Py_XDECREF(pyOverride); + resultCache = Py_None; + Py_INCREF(resultCache); + gil.release(); + return nullptr; // // Give up. + } + + resultCache = pyOverride; + // recreate the callable from function/self + return PepExt_Type_CallDescrGet(resultCache, pySelf, nullptr); } namespace diff --git a/sources/shiboken6/libshiboken/basewrapper.h b/sources/shiboken6/libshiboken/basewrapper.h index 9eea89540..426298bcf 100644 --- a/sources/shiboken6/libshiboken/basewrapper.h +++ b/sources/shiboken6/libshiboken/basewrapper.h @@ -123,9 +123,8 @@ LIBSHIBOKEN_API PyObject *Sbk_ReturnFromPython_Self(PyObject *self); } // extern "C" LIBSHIBOKEN_API PyObject *Sbk_GetPyOverride(const void *voidThis, PyTypeObject *typeObject, - Shiboken::GilState &gil, - const char *funcName, bool &resultCache, - PyObject **nameCache); + Shiboken::GilState &gil, const char *funcName, + PyObject *&resultCache, PyObject **nameCache); namespace Shiboken { diff --git a/sources/shiboken6/libshiboken/bindingmanager.cpp b/sources/shiboken6/libshiboken/bindingmanager.cpp index 25cc5c00a..ca509aefd 100644 --- a/sources/shiboken6/libshiboken/bindingmanager.cpp +++ b/sources/shiboken6/libshiboken/bindingmanager.cpp @@ -369,13 +369,6 @@ SbkObject *BindingManager::retrieveWrapper(const void *cptr, PyTypeObject *typeO PyObject *BindingManager::getOverride(SbkObject *wrapper, PyObject *pyMethodName) { auto *obWrapper = reinterpret_cast<PyObject *>(wrapper); - auto *wrapper_dict = SbkObject_GetDict_NoRef(obWrapper); - if (PyObject *method = PyDict_GetItem(wrapper_dict, pyMethodName)) { - // Note: This special case was implemented for duck-punching, which happens - // in the instance dict. It does not work with properties. - Py_INCREF(method); - return method; - } Shiboken::AutoDecRef method(PyObject_GetAttr(obWrapper, pyMethodName)); if (method.isNull()) @@ -387,9 +380,9 @@ PyObject *BindingManager::getOverride(SbkObject *wrapper, PyObject *pyMethodName // crude check for them. // PYSIDE-535: This macro is redefined in a compatible way in pep384 if (PyMethod_Check(method) != 0) { - if (PyMethod_GET_SELF(method) != obWrapper) + if (PyMethod_Self(method) != obWrapper) return nullptr; - function = PyMethod_GET_FUNCTION(method); + function = PyMethod_Function(method); } else if (isCompiledMethod(method)) { Shiboken::AutoDecRef im_self(PyObject_GetAttr(method, PyName::im_self())); // Not retaining a reference inline with what PyMethod_GET_SELF does. @@ -413,13 +406,13 @@ PyObject *BindingManager::getOverride(SbkObject *wrapper, PyObject *pyMethodName if (PyObject *defaultMethod = PyDict_GetItem(parentDict.object(), pyMethodName)) { defaultFound = true; if (function != defaultMethod) - return method.release(); + return function; } } } // PYSIDE-2255: If no default method was found, use the method. if (!defaultFound) - return method.release(); + return function; return nullptr; } diff --git a/sources/shiboken6/libshiboken/sbkconverter.cpp b/sources/shiboken6/libshiboken/sbkconverter.cpp index 94bb1e2b4..1f79402b7 100644 --- a/sources/shiboken6/libshiboken/sbkconverter.cpp +++ b/sources/shiboken6/libshiboken/sbkconverter.cpp @@ -451,8 +451,11 @@ void nonePythonToCppNullPtr(PyObject *, void *cppOut) void *cppPointer(PyTypeObject *desiredType, SbkObject *pyIn) { assert(pyIn); - if (!ObjectType::checkType(desiredType)) + if (!ObjectType::checkType(desiredType)) { + std::cerr << __FUNCTION__ << ": Conversion to non SbkObject type " << desiredType->tp_name + << " requested, falling back to pass-through.\n"; return pyIn; + } auto *inType = Py_TYPE(pyIn); if (ObjectType::hasCast(inType)) return ObjectType::cast(inType, pyIn, desiredType); @@ -485,8 +488,14 @@ static void _pythonToCppCopy(const SbkConverter *converter, PyObject *pyIn, void assert(pyIn); assert(cppOut); PythonToCppFunc toCpp = IsPythonToCppConvertible(converter, pyIn); - if (toCpp) + if (toCpp) { toCpp(pyIn, cppOut); + } else { + std::cerr << __FUNCTION__ << ": Cannot copy-convert " << pyIn; + if (pyIn) + std::cerr << " (" << Py_TYPE(pyIn)->tp_name << ')'; + std::cerr << " to C++.\n"; + } } void pythonToCppCopy(PyTypeObject *type, PyObject *pyIn, void *cppOut) diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/layout.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/layout.py index 3f83b0816..8eea431c3 100644 --- a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/layout.py +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/layout.py @@ -30,6 +30,7 @@ from textwrap import dedent from shibokensupport.signature.mapping import ellipsis, missing_optional_return, PlaceholderType from shibokensupport.signature.parser import using_snake_case from shibokensupport.signature import make_snake_case_name +from collections.abc import Sequence, Iterable DEFAULT_PARAM_KIND = inspect.Parameter.POSITIONAL_ONLY @@ -134,9 +135,14 @@ _KEYWORD_ONLY = inspect.Parameter.KEYWORD_ONLY # noqa E:201 _VAR_KEYWORD = inspect.Parameter.VAR_KEYWORD # noqa E:201 _empty = inspect.Parameter.empty # noqa E:201 - +# PYSIDE-3098: Iterable and Sequence can occur together in an overload. +# This needs sorting in order of generality. +# This happened in the NumPy support of PySide 6.10. Note that the ordering +# of methods there is completely different and unrelated to this mypy sorting. default_weights = { typing.Any: 1000, # noqa E:241 + Iterable: 500, # noqa E:241 + Sequence: 400, # noqa E:241 bool: 101, # noqa E:241 int: 102, # noqa E:241 float: 103, # noqa E:241 @@ -346,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 @@ -367,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/shibokenmodule/files.dir/shibokensupport/signature/mapping.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/mapping.py index 2bb3c9a87..84cb15feb 100644 --- a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/mapping.py +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/mapping.py @@ -509,6 +509,7 @@ def init_PySide6_QtCore(): "PySide6.QtCore.QUrl.ComponentFormattingOptions": PySide6.QtCore.QUrl.ComponentFormattingOption, # mismatch option/enum, why??? "PyUnicode": typing.Text, + "QByteArray": typing.Union[PySide6.QtCore.QByteArray, bytes, bytearray, memoryview], "QByteArrayView": PySide6.QtCore.QByteArray, "Q_NULLPTR": None, "QCalendar.Unspecified": PySide6.QtCore.QCalendar.Unspecified, diff --git a/sources/shiboken6/tests/libsample/bucket.cpp b/sources/shiboken6/tests/libsample/bucket.cpp index cafd382a9..43a32f414 100644 --- a/sources/shiboken6/tests/libsample/bucket.cpp +++ b/sources/shiboken6/tests/libsample/bucket.cpp @@ -40,15 +40,21 @@ bool Bucket::empty() void Bucket::lock() { - m_locked = true; - while (m_locked) { - SLEEP(300); + bool expected = false; + if (m_locked.compare_exchange_strong(expected, true)) { + while (m_locked) { + SLEEP(300); + } + } else { + std::cerr << __FUNCTION__ << " Attempt to lock twice.\n"; } } void Bucket::unlock() { - m_locked = false; + bool expected = true; + if (!m_locked.compare_exchange_strong(expected, false)) + std::cerr << __FUNCTION__ << " Attempt to unlock twice.\n"; } bool Bucket::virtualBlockerMethod() diff --git a/sources/shiboken6/tests/libsample/bucket.h b/sources/shiboken6/tests/libsample/bucket.h index 73e8edd78..677069fe7 100644 --- a/sources/shiboken6/tests/libsample/bucket.h +++ b/sources/shiboken6/tests/libsample/bucket.h @@ -7,6 +7,7 @@ #include "libsamplemacros.h" #include "objecttype.h" +#include <atomic> #include <list> class ObjectType; @@ -19,7 +20,7 @@ public: int pop(); bool empty(); void lock(); - inline bool locked() { return m_locked; } + bool locked() { return m_locked.load(); } void unlock(); virtual bool virtualBlockerMethod(); @@ -27,8 +28,7 @@ public: private: std::list<int> m_data; - - volatile bool m_locked = false; + std::atomic<bool> m_locked{false}; }; #endif // BUCKET_H 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"/> |