diff options
author | Friedemann Kleint <Friedemann.Kleint@qt.io> | 2021-07-14 14:15:02 +0200 |
---|---|---|
committer | Friedemann Kleint <Friedemann.Kleint@qt.io> | 2021-09-24 13:52:35 +0200 |
commit | bce1bfb3af99aeb24259df34d662e8fcf072d3fd (patch) | |
tree | ef36333a2b059f4278cc5aad4efa860f9e4cd30b | |
parent | 79b32f4d4b5154ba8001bafc481fb6edacc10280 (diff) |
shiboken6: Add opaque containers for C++ sequence containers
Add a class that directly wraps a C++ sequence container,
allow for modifying them.
For all instantiated containers, generate a special (sequence) type
that wraps the C++ container directly. For example, it will be
accessible as a QList_int.
This is achieved via providing a template for a type private
that relies on a conversion traits template for conversion.
Only the conversion traits specialization code needs to be generated.
Use cases:
- Allowing for modifying Fields of such container types
(non-owning)
- Pass it into functions taking such containers instead of converting
back and forth from a PyList (constructed in Python, owning)
[ChangeLog][shiboken6] Support for opaque C++ sequence scontainers
has been added, allowing to pass a wrapped C++ container
directly instead of converting it back and forth from
Python sequences.
Task-number: PYSIDE-1605
Change-Id: I49d378eb1a0151730d817d5bdd4b71a7c3b5cdda
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Cristian Maureira-Fredes <cristian.maureira-fredes@qt.io>
30 files changed, 935 insertions, 17 deletions
diff --git a/examples/widgets/painting/plot/plot.py b/examples/widgets/painting/plot/plot.py new file mode 100644 index 000000000..156a6408d --- /dev/null +++ b/examples/widgets/painting/plot/plot.py @@ -0,0 +1,105 @@ +############################################################################# +## +## Copyright (C) 2021 The Qt Company Ltd. +## Contact: https://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +import math +import sys + +from PySide6.QtWidgets import QWidget, QApplication +from PySide6.QtCore import QPoint, QRect, QTimer, Qt, Slot +from PySide6.QtGui import (QColor, QPainter, QPaintEvent, QPen, QPointList, + QTransform) + + +WIDTH = 680 +HEIGHT = 480 + + +class PlotWidget(QWidget): + """Illustrates the use of opaque containers. QPointList + wraps a C++ QList<QPoint> directly, removing the need to convert + a Python list in each call to QPainter.drawPolyline().""" + + def __init__(self, parent=None): + super().__init__(parent) + self._timer = QTimer(self) + self._timer.setInterval(20) + self._timer.timeout.connect(self.shift) + + self._points = QPointList() + self._x = 0 + self._delta_x = 0.05 + self._half_height = HEIGHT / 2 + self._factor = 0.8 * self._half_height + + for i in range(WIDTH): + self._points.append(QPoint(i, self.next_point())) + + self.setFixedSize(WIDTH, HEIGHT) + + self._timer.start() + + def next_point(self): + result = self._half_height - self._factor * math.sin(self._x) + self._x += self._delta_x + return result + + def shift(self): + last_x = self._points[WIDTH - 1].x() + self._points.pop_front() + self._points.append(QPoint(last_x + 1, self.next_point())) + self.update() + + def paintEvent(self, event): + painter = QPainter() + painter.begin(self) + rect = QRect(QPoint(0, 0), self.size()) + painter.fillRect(rect, Qt.white) + painter.translate(-self._points[0].x(), 0) + painter.drawPolyline(self._points) + painter.end() + + +if __name__ == "__main__": + + app = QApplication(sys.argv) + + w = PlotWidget() + w.show() + sys.exit(app.exec()) diff --git a/examples/widgets/painting/plot/plot.pyproject b/examples/widgets/painting/plot/plot.pyproject new file mode 100644 index 000000000..0ac776c83 --- /dev/null +++ b/examples/widgets/painting/plot/plot.pyproject @@ -0,0 +1,3 @@ +{ + "files": ["plot.py"] +} diff --git a/sources/pyside6/PySide6/QtCore/typesystem_core_common.xml b/sources/pyside6/PySide6/QtCore/typesystem_core_common.xml index fad2ee16c..30d5f3d7b 100644 --- a/sources/pyside6/PySide6/QtCore/typesystem_core_common.xml +++ b/sources/pyside6/PySide6/QtCore/typesystem_core_common.xml @@ -405,7 +405,8 @@ </container-type> <!-- FIXME: Which one is it going to be? --> - <container-type name="QList" type="list"> + <container-type name="QList" type="list" + opaque-containers="int:QIntList;QPoint:QPointList;QPointF:QPointFList"> <include file-name="QList" location="global"/> <declare-function signature="append(T)" return-type="void"/> <declare-function signature="insert(qsizetype,T)" return-type="void"/> diff --git a/sources/shiboken6/ApiExtractor/abstractmetafield.cpp b/sources/shiboken6/ApiExtractor/abstractmetafield.cpp index 44e8ddc84..45ea5601a 100644 --- a/sources/shiboken6/ApiExtractor/abstractmetafield.cpp +++ b/sources/shiboken6/ApiExtractor/abstractmetafield.cpp @@ -87,6 +87,16 @@ bool AbstractMetaField::isModifiedRemoved(int types) const return false; } +bool AbstractMetaField::generateOpaqueContainer() const +{ + const FieldModificationList &mods = modifications(); + for (const FieldModification &mod : mods) { + if (mod.isOpaqueContainer()) + return true; + } + return false; +} + const AbstractMetaType &AbstractMetaField::type() const { return d->m_type; diff --git a/sources/shiboken6/ApiExtractor/abstractmetafield.h b/sources/shiboken6/ApiExtractor/abstractmetafield.h index e6435f68d..794cce462 100644 --- a/sources/shiboken6/ApiExtractor/abstractmetafield.h +++ b/sources/shiboken6/ApiExtractor/abstractmetafield.h @@ -57,6 +57,7 @@ public: FieldModificationList modifications() const; bool isModifiedRemoved(int types = TypeSystem::All) const; + bool generateOpaqueContainer() const; const AbstractMetaType &type() const; void setType(const AbstractMetaType &type); diff --git a/sources/shiboken6/ApiExtractor/abstractmetatype.cpp b/sources/shiboken6/ApiExtractor/abstractmetatype.cpp index f099bba08..8e6238448 100644 --- a/sources/shiboken6/ApiExtractor/abstractmetatype.cpp +++ b/sources/shiboken6/ApiExtractor/abstractmetatype.cpp @@ -768,7 +768,8 @@ bool AbstractMetaType::shouldDereferenceArgument() const { return isWrapperPassedByReference() || valueTypeWithCopyConstructorOnlyPassed() - || isObjectTypeUsedAsValueType(); + || isObjectTypeUsedAsValueType() + || generateOpaqueContainer(); } bool AbstractMetaType::isCppIntegralPrimitive() const @@ -850,6 +851,36 @@ AbstractMetaType AbstractMetaType::fromAbstractMetaClass(const AbstractMetaClass return fromTypeEntry(metaClass->typeEntry()); } +bool AbstractMetaType::generateOpaqueContainer() const +{ + if (!isContainer()) + return false; + auto *containerTypeEntry = static_cast<const ContainerTypeEntry *>(typeEntry()); + auto kind = containerTypeEntry->containerKind(); + if (kind != ContainerTypeEntry::ListContainer) + return false; + const auto &instantation = d->m_instantiations.constFirst(); + if (instantation.referenceType() != NoReference) + return false; + const QString signature = instantation.cppSignature(); + + bool result = false; + auto *instTypEntry = instantation.typeEntry(); + switch (instTypEntry->type()) { + case TypeEntry::PrimitiveType: + case TypeEntry::FlagsType: + case TypeEntry::EnumType: + case TypeEntry::BasicValueType: + case TypeEntry::ObjectType: + case TypeEntry::CustomType: + result = containerTypeEntry->generateOpaqueContainer(signature); + break; + default: + break; + } + return result; +} + #ifndef QT_NO_DEBUG_STREAM void AbstractMetaType::formatDebug(QDebug &debug) const { diff --git a/sources/shiboken6/ApiExtractor/abstractmetatype.h b/sources/shiboken6/ApiExtractor/abstractmetatype.h index 79523efef..98017826e 100644 --- a/sources/shiboken6/ApiExtractor/abstractmetatype.h +++ b/sources/shiboken6/ApiExtractor/abstractmetatype.h @@ -247,6 +247,8 @@ public: /// copy constructor only is passed as value or const-ref and thus /// no default value can be constructed. bool valueTypeWithCopyConstructorOnlyPassed() const; + /// Returns whether to generate an opaque container for the type + bool generateOpaqueContainer() const; #ifndef QT_NO_DEBUG_STREAM void formatDebug(QDebug &debug) const; @@ -266,6 +268,9 @@ inline bool operator==(const AbstractMetaType &t1, const AbstractMetaType &t2) inline bool operator!=(const AbstractMetaType &t1, const AbstractMetaType &t2) { return !t1.equals(t2); } +inline size_t qHash(const AbstractMetaType &t, size_t seed) +{ return qHash(t.typeEntry(), seed); } + #ifndef QT_NO_DEBUG_STREAM QDebug operator<<(QDebug d, const AbstractMetaType &at); QDebug operator<<(QDebug d, const AbstractMetaType *at); diff --git a/sources/shiboken6/ApiExtractor/modifications.cpp b/sources/shiboken6/ApiExtractor/modifications.cpp index 118b511e6..8c6f31187 100644 --- a/sources/shiboken6/ApiExtractor/modifications.cpp +++ b/sources/shiboken6/ApiExtractor/modifications.cpp @@ -108,6 +108,7 @@ public: bool m_readable = true; bool m_writable = true; bool m_removed = false; + bool m_opaqueContainer = false; TypeSystem::SnakeCase snakeCase = TypeSystem::SnakeCase::Unspecified; }; @@ -181,6 +182,17 @@ void FieldModification::setRemoved(bool r) d->m_removed = r; } +bool FieldModification::isOpaqueContainer() const +{ + return d->m_opaqueContainer; +} + +void FieldModification::setOpaqueContainer(bool r) +{ + if (d->m_opaqueContainer != r) + d->m_opaqueContainer = r; +} + TypeSystem::SnakeCase FieldModification::snakeCase() const { return d->snakeCase; diff --git a/sources/shiboken6/ApiExtractor/modifications.h b/sources/shiboken6/ApiExtractor/modifications.h index b97d7f292..291f80545 100644 --- a/sources/shiboken6/ApiExtractor/modifications.h +++ b/sources/shiboken6/ApiExtractor/modifications.h @@ -413,6 +413,9 @@ public: bool isRemoved() const; void setRemoved(bool r); + bool isOpaqueContainer() const; + void setOpaqueContainer(bool r); + TypeSystem::SnakeCase snakeCase() const; void setSnakeCase(TypeSystem::SnakeCase s); diff --git a/sources/shiboken6/ApiExtractor/typesystem.cpp b/sources/shiboken6/ApiExtractor/typesystem.cpp index 4a34046df..b01281da6 100644 --- a/sources/shiboken6/ApiExtractor/typesystem.cpp +++ b/sources/shiboken6/ApiExtractor/typesystem.cpp @@ -1610,6 +1610,9 @@ TypedefEntry::TypedefEntry(TypedefEntryPrivate *d) : class ContainerTypeEntryPrivate : public ComplexTypeEntryPrivate { public: + using OpaqueContainer = ContainerTypeEntry::OpaqueContainer; + using OpaqueContainers = ContainerTypeEntry::OpaqueContainers; + ContainerTypeEntryPrivate(const QString &entryName, ContainerTypeEntry::ContainerKind containerKind, const QVersionNumber &vr, @@ -1619,6 +1622,15 @@ public: { } + OpaqueContainers::const_iterator findOpaqueContainer(const QString &instantiation) const + { + return std::find_if(m_opaqueContainers.cbegin(), m_opaqueContainers.cend(), + [&instantiation](const OpaqueContainer &r) { + return r.instantiation == instantiation; + }); + } + + OpaqueContainers m_opaqueContainers; ContainerTypeEntry::ContainerKind m_containerKind; }; @@ -1636,6 +1648,36 @@ ContainerTypeEntry::ContainerKind ContainerTypeEntry::containerKind() const return d->m_containerKind; } +const ContainerTypeEntry::OpaqueContainers &ContainerTypeEntry::opaqueContainers() const +{ + S_D(const ContainerTypeEntry); + return d->m_opaqueContainers; +} + +void ContainerTypeEntry::addOpaqueContainer(OpaqueContainer r) +{ + S_D(ContainerTypeEntry); + // Fix to match AbstractMetaType::signature() which is used for matching + // "Foo*" -> "Foo *" + const auto asteriskPos = r.instantiation.indexOf(u'*'); + if (asteriskPos > 0 && !r.instantiation.at(asteriskPos - 1).isSpace()) + r.instantiation.insert(asteriskPos, u' '); + d->m_opaqueContainers.append(r); +} + +bool ContainerTypeEntry::generateOpaqueContainer(const QString &instantiation) const +{ + S_D(const ContainerTypeEntry); + return d->findOpaqueContainer(instantiation) != d->m_opaqueContainers.cend(); +} + +QString ContainerTypeEntry::opaqueContainerName(const QString &instantiation) const +{ + S_D(const ContainerTypeEntry); + const auto it = d->findOpaqueContainer(instantiation); + return it != d->m_opaqueContainers.cend() ? it->name : QString{}; +} + TypeEntry *ContainerTypeEntry::clone() const { S_D(const ContainerTypeEntry); @@ -2215,6 +2257,12 @@ void ContainerTypeEntry::formatDebug(QDebug &debug) const ComplexTypeEntry::formatDebug(debug); debug << ", type=" << d->m_containerKind << '"'; + if (!d->m_opaqueContainers.isEmpty()) { + debug << ", opaque-containers=["; + for (const auto &r : d->m_opaqueContainers) + debug << r.instantiation << "->" << r.name << ','; + debug << ']'; + } } void SmartPointerTypeEntry::formatDebug(QDebug &debug) const diff --git a/sources/shiboken6/ApiExtractor/typesystem.h b/sources/shiboken6/ApiExtractor/typesystem.h index 54eaf4e40..1ab27b4f4 100644 --- a/sources/shiboken6/ApiExtractor/typesystem.h +++ b/sources/shiboken6/ApiExtractor/typesystem.h @@ -640,6 +640,13 @@ class ContainerTypeEntry : public ComplexTypeEntry { Q_GADGET public: + struct OpaqueContainer // Generate an opaque container for an instantiation under name + { + QString instantiation; + QString name; + }; + using OpaqueContainers = QList<OpaqueContainer>; + enum ContainerKind { ListContainer, SetContainer, @@ -654,6 +661,11 @@ public: ContainerKind containerKind() const; + const OpaqueContainers &opaqueContainers() const; + void addOpaqueContainer(OpaqueContainer r); + bool generateOpaqueContainer(const QString &instantiation) const; + QString opaqueContainerName(const QString &instantiation) const; + TypeEntry *clone() const override; #ifndef QT_NO_DEBUG_STREAM diff --git a/sources/shiboken6/ApiExtractor/typesystemparser.cpp b/sources/shiboken6/ApiExtractor/typesystemparser.cpp index 7ba7860f4..fe1761dd8 100644 --- a/sources/shiboken6/ApiExtractor/typesystemparser.cpp +++ b/sources/shiboken6/ApiExtractor/typesystemparser.cpp @@ -95,6 +95,7 @@ static inline QString renameAttribute() { return QStringLiteral("rename"); } static inline QString readAttribute() { return QStringLiteral("read"); } static inline QString targetLangNameAttribute() { return QStringLiteral("target-lang-name"); } static inline QString writeAttribute() { return QStringLiteral("write"); } +static inline QString opaqueContainerFieldAttribute() { return QStringLiteral("opaque-container"); } static inline QString replaceAttribute() { return QStringLiteral("replace"); } static inline QString toAttribute() { return QStringLiteral("to"); } static inline QString signatureAttribute() { return QStringLiteral("signature"); } @@ -1359,6 +1360,21 @@ PrimitiveTypeEntry * return type; } +// "int:QList_int;QString:QList_QString" +static bool parseOpaqueContainers(QStringView s, ContainerTypeEntry *cte) +{ + const auto entries = s.split(u';'); + for (const auto &entry : entries) { + const auto values = entry.split(u':'); + if (values.size() != 2) + return false; + QString instantiation = values.at(0).trimmed().toString(); + QString name = values.at(1).trimmed().toString(); + cte->addOpaqueContainer({instantiation, name}); + } + return true; +} + ContainerTypeEntry * TypeSystemParser::parseContainerTypeEntry(const ConditionalStreamReader &reader, const QString &name, const QVersionNumber &since, @@ -1382,6 +1398,19 @@ ContainerTypeEntry * since, currentParentTypeEntry()); if (!applyCommonAttributes(reader, type, attributes)) return nullptr; + + for (int i = attributes->size() - 1; i >= 0; --i) { + const auto name = attributes->at(i).qualifiedName(); + if (name == u"opaque-containers") { + const auto attribute = attributes->takeAt(i); + if (!parseOpaqueContainers(attribute.value(), type)) { + m_error = u"Error parsing the opaque container attribute: \""_qs + + attribute.value().toString() + u"\"."_qs; + return nullptr; + } + } + } + return type; } @@ -2219,6 +2248,9 @@ bool TypeSystemParser::parseModifyField(const ConditionalStreamReader &, fm.setName(attributes->takeAt(i).value().toString()); } else if (name == removeAttribute()) { fm.setRemoved(convertRemovalAttribute(attributes->takeAt(i).value())); + } else if (name == opaqueContainerFieldAttribute()) { + fm.setOpaqueContainer(convertBoolean(attributes->takeAt(i).value(), + opaqueContainerFieldAttribute(), false)); } else if (name == readAttribute()) { fm.setReadable(convertBoolean(attributes->takeAt(i).value(), readAttribute(), true)); } else if (name == writeAttribute()) { diff --git a/sources/shiboken6/doc/typesystem.rst b/sources/shiboken6/doc/typesystem.rst index 0f80da40b..b083f9c66 100644 --- a/sources/shiboken6/doc/typesystem.rst +++ b/sources/shiboken6/doc/typesystem.rst @@ -40,6 +40,7 @@ Modifying types typesystem_arguments.rst typesystem_codeinjection.rst typesystem_converters.rst + typesystem_containers.rst typesystem_templates.rst typesystem_modify_function.rst typesystem_manipulating_objects.rst diff --git a/sources/shiboken6/doc/typesystem_containers.rst b/sources/shiboken6/doc/typesystem_containers.rst new file mode 100644 index 000000000..d74563b04 --- /dev/null +++ b/sources/shiboken6/doc/typesystem_containers.rst @@ -0,0 +1,45 @@ +.. _opaque-containers: + +***************** +Opaque Containers +***************** + +Normally, Python containers such as ``list`` or ``dict`` are passed when +calling C++ functions taking a corresponding C++ container (see +:ref:`container-type`). + +This means that for each call, the entire Python container is converted to +a C++ container, which can be inefficient when for example creating plots +from lists of points. + +To work around this, special opaque containers can generated which wrap an +underlying C++ container directly (currently implemented for ``list`` types). +They implement the sequence protocol and can be passed to the function +instead of a Python list. Manipulations like adding or removing elements +can applied directly to them using the C++ container functions. + +This is achieved by specifying the name and the instantiated type +in the ``opaque-containers`` attribute of :ref:`container-type`. + +A second use case are public fields of container types. In the normal case, +they are converted to Python containers on read access. By a field modification, +(see :ref:`modify-field`), it is possible to obtain an opaque container +which avoids the conversion and allows for direct modification of elements. + +The table below lists the functions supported for opaque sequence containers +besides the sequence protocol (element access via index and ``len()``). Both +the STL and the Qt naming convention (which resembles Python's) are supported: + + +-------------------------------------------+-----------------------------------+ + |Function | Description | + +-------------------------------------------+-----------------------------------+ + | ``push_back(value)``, ``append(value)`` | Appends *value* to the sequence. | + +-------------------------------------------+-----------------------------------+ + | ``push_front(value)``, ``prepend(value)`` | Prepends *value* to the sequence. | + +-------------------------------------------+-----------------------------------+ + | ``clear()`` | Clears the sequence. | + +-------------------------------------------+-----------------------------------+ + | ``pop_back()``, ``removeLast()`` | Removes the last element. | + +-------------------------------------------+-----------------------------------+ + | ``pop_front()``, ``removeFirst()`` | Removes the first element. | + +-------------------------------------------+-----------------------------------+ diff --git a/sources/shiboken6/doc/typesystem_manipulating_objects.rst b/sources/shiboken6/doc/typesystem_manipulating_objects.rst index 1e9a4569e..79add6d57 100644 --- a/sources/shiboken6/doc/typesystem_manipulating_objects.rst +++ b/sources/shiboken6/doc/typesystem_manipulating_objects.rst @@ -109,6 +109,8 @@ Using Snippets From External Files will be extracted. +.. _modify-field: + modify-field ^^^^^^^^^^^^ @@ -123,6 +125,7 @@ modify-field write="true | false" read="true | false" remove="true | false" + opaque-container = "yes | no" snake-case="yes | no | both" /> </object-type> @@ -136,6 +139,10 @@ modify-field The *optional* ``rename`` attribute can be used to change the name of the given field in the generated target language API. + The *optional* ``opaque-container`` attribute specifies whether + an opaque container should be returned on read access + (see :ref:`opaque-containers`). + The *optional* **snake-case** attribute allows for overriding the value specified on the class entry or **typesystem** element. diff --git a/sources/shiboken6/doc/typesystem_specifying_types.rst b/sources/shiboken6/doc/typesystem_specifying_types.rst index 3b2e979b3..5fe88d167 100644 --- a/sources/shiboken6/doc/typesystem_specifying_types.rst +++ b/sources/shiboken6/doc/typesystem_specifying_types.rst @@ -456,7 +456,8 @@ container-type <typesystem> <container-type name="..." since="..." - type ="..." /> + type ="..." + opaque-containers ="..." /> </typesystem> The **name** attribute is the fully qualified C++ class name. The **type** @@ -467,6 +468,10 @@ container-type *stack* and *queue* are equivalent to *list*. *hash* and *multi-hash* are equivalent to *map* and *multi-map*, respectively. + The *optional* **opaque-containers** attribute specifies a semi-colon separated + list of colon separated pairs of instantiation and name for + :ref:`opaque-containers`. + The *optional* **since** value is used to specify the API version of this container. .. _typedef-type: diff --git a/sources/shiboken6/generator/CMakeLists.txt b/sources/shiboken6/generator/CMakeLists.txt index 0296138e5..4cb154a79 100644 --- a/sources/shiboken6/generator/CMakeLists.txt +++ b/sources/shiboken6/generator/CMakeLists.txt @@ -3,6 +3,7 @@ project(shibokengenerator) set(shiboken6_SRC generator.cpp shiboken/cppgenerator.cpp +shiboken/cppgenerator_container.cpp shiboken/headergenerator.cpp shiboken/overloaddata.cpp shiboken/shibokengenerator.cpp diff --git a/sources/shiboken6/generator/shiboken/cppgenerator.cpp b/sources/shiboken6/generator/shiboken/cppgenerator.cpp index e7648f736..8e1a2c934 100644 --- a/sources/shiboken6/generator/shiboken/cppgenerator.cpp +++ b/sources/shiboken6/generator/shiboken/cppgenerator.cpp @@ -1218,7 +1218,8 @@ void CppGenerator::writeVirtualMethodNative(TextStream &s, writeConversionRule(s, func, TypeSystem::NativeCode, QLatin1String(CPP_RETURN_VAR)); } else if (!func->injectedCodeHasReturnValueAttribution(TypeSystem::NativeCode)) { writePythonToCppTypeConversion(s, func->type(), QLatin1String(PYTHON_RETURN_VAR), - QLatin1String(CPP_RETURN_VAR), func->implementingClass()); + QLatin1String(CPP_RETURN_VAR), func->implementingClass(), {}, + PythonToCppTypeConversionFlag::DisableOpaqueContainers); } } } @@ -2578,7 +2579,8 @@ void CppGenerator::writePythonToCppTypeConversion(TextStream &s, const QString &pyIn, const QString &cppOut, const AbstractMetaClass *context, - const QString &defaultValue) const + const QString &defaultValue, + PythonToCppTypeConversionFlags flags) const { const TypeEntry *typeEntry = type.typeEntry(); if (typeEntry->isCustom() || typeEntry->isVarargs()) @@ -2590,6 +2592,9 @@ void CppGenerator::writePythonToCppTypeConversion(TextStream &s, const bool isEnum = typeEntry->isEnum(); const bool isFlags = typeEntry->isFlags(); const bool treatAsPointer = type.valueTypeWithCopyConstructorOnlyPassed(); + const bool maybeOpaqueContainer = + !flags.testFlag(PythonToCppTypeConversionFlag::DisableOpaqueContainers) + && type.generateOpaqueContainer(); bool isPointerOrObjectType = (type.isObjectType() || type.isPointer()) && !type.isUserPrimitive() && !type.isExtendedCppPrimitive() && !isEnum && !isFlags; @@ -2603,8 +2608,9 @@ void CppGenerator::writePythonToCppTypeConversion(TextStream &s, // For implicit conversions or containers, either value or pointer conversion // may occur. An implicit conversion uses value conversion whereas the object - // itself uses pointer conversion. - const bool valueOrPointer = mayHaveImplicitConversion; + // itself uses pointer conversion. For containers, the PyList/container + // conversion is by value whereas opaque containers use pointer conversion. + const bool valueOrPointer = mayHaveImplicitConversion || maybeOpaqueContainer; const AbstractMetaTypeList &nestedArrayTypes = type.nestedArrayTypes(); const bool isCppPrimitiveArray = !nestedArrayTypes.isEmpty() @@ -2665,7 +2671,7 @@ void CppGenerator::writePythonToCppTypeConversion(TextStream &s, QString pythonToCppCall = pythonToCppFunc + u'(' + pyIn + u", &"_qs + cppOut + u')'; - if (!mayHaveImplicitConversion) { + if (!valueOrPointer) { // pythonToCppFunc may be 0 when less parameters are passed and // the defaultValue takes effect. if (!defaultValue.isEmpty()) @@ -3306,7 +3312,8 @@ void CppGenerator::writePythonToCppConversionFunctions(TextStream &s, const Abst for (int i = 0; i < containerType.instantiations().count(); ++i) { const AbstractMetaType &type = containerType.instantiations().at(i); QString typeName = getFullTypeName(type); - if (type.shouldDereferenceArgument()) { + // Containers of opaque containers are not handled here. + if (type.shouldDereferenceArgument() && !type.generateOpaqueContainer()) { for (int pos = 0; ; ) { const QRegularExpressionMatch match = convertToCppRegEx().match(code, pos); if (!match.hasMatch()) @@ -4126,7 +4133,7 @@ void CppGenerator::writeEnumConverterInitialization(TextStream &s, const TypeEnt writeEnumConverterInitialization(s, static_cast<const EnumTypeEntry *>(enumType)->flags()); } -void CppGenerator::writeContainerConverterInitialization(TextStream &s, const AbstractMetaType &type) const +QString CppGenerator::writeContainerConverterInitialization(TextStream &s, const AbstractMetaType &type) const { QByteArray cppSignature = QMetaObject::normalizedSignature(type.cppSignature().toUtf8()); s << "// Register converter for type '" << cppSignature << "'.\n"; @@ -4150,7 +4157,9 @@ void CppGenerator::writeContainerConverterInitialization(TextStream &s, const Ab cppSignature.remove(0, sizeof("const ") / sizeof(char) - 1); s << "Shiboken::Conversions::registerConverterName(" << converter << ", \"" << cppSignature << "\");\n"; } - writeAddPythonToCppConversion(s, converterObject(type), toCpp, isConv); + const QString converterObj = converterObject(type); + writeAddPythonToCppConversion(s, converterObj, toCpp, isConv); + return converterObj; } void CppGenerator::writeSmartPointerConverterInitialization(TextStream &s, const AbstractMetaType &type) const @@ -4723,6 +4732,22 @@ void CppGenerator::writeGetterFunction(TextStream &s, && !fieldType.isPointer(); QString cppField = cppFieldAccess(metaField, context); + + if (metaField.generateOpaqueContainer() + && fieldType.generateOpaqueContainer()) { + const auto *containerTypeEntry = + static_cast<const ContainerTypeEntry *>(fieldType.typeEntry()); + const auto *instantiationTypeEntry = + fieldType.instantiations().constFirst().typeEntry(); + const QString creationFunc = + u"create"_qs + containerTypeEntry->opaqueContainerName(instantiationTypeEntry->name()); + s << "PyObject *" << creationFunc << '(' << fieldType.cppSignature() << "*);\n" + << "PyObject *pyOut = " << creationFunc + << "(&" << cppField << ");\nPy_IncRef(pyOut);\n" + << "return pyOut;\n" << outdent << "}\n"; + return; + } + if (newWrapperSameObject) { cppField.prepend(u"&("); cppField.append(u')'); @@ -6143,6 +6168,10 @@ bool CppGenerator::finishGeneration() #include <algorithm> #include <signature.h> )"; + + if (!instantiatedContainers().isEmpty()) + s << "#include <sbkcontainer.h>\n#include <sbkstaticstrings.h>\n"; + if (usePySideExtensions()) { s << includeQDebug; s << R"(#include <pyside.h> @@ -6269,12 +6298,17 @@ bool CppGenerator::finishGeneration() s << '\n'; } + QHash<AbstractMetaType, OpaqueContainerData> opaqueContainers; const auto &containers = instantiatedContainers(); if (!containers.isEmpty()) { s << "// Container Type converters.\n\n"; for (const AbstractMetaType &container : containers) { s << "// C++ to Python conversion for container type '" << container.cppSignature() << "'.\n"; writeContainerConverterFunctions(s, container); + if (container.generateOpaqueContainer()) { + opaqueContainers.insert(container, + writeOpaqueContainerConverterFunctions(s, container)); + } } s << '\n'; } @@ -6358,11 +6392,25 @@ bool CppGenerator::finishGeneration() if (!containers.isEmpty()) { s << '\n'; for (const AbstractMetaType &container : containers) { - writeContainerConverterInitialization(s, container); + const QString converterObj = writeContainerConverterInitialization(s, container); + const auto it = opaqueContainers.constFind(container); + if (it != opaqueContainers.constEnd()) { + writeSetPythonToCppPointerConversion(s, converterObj, + it.value().pythonToConverterFunctionName, + it.value().converterCheckFunctionName); + } s << '\n'; } } + if (!opaqueContainers.isEmpty()) { + s << "\n// Opaque container type registration\n" + << "PyObject *ob_type{};\n"; + for (const auto &d : opaqueContainers) + s << d.registrationCode; + s << '\n'; + } + if (!smartPointersList.isEmpty()) { s << '\n'; for (const AbstractMetaType &smartPointer : smartPointersList) { diff --git a/sources/shiboken6/generator/shiboken/cppgenerator.h b/sources/shiboken6/generator/shiboken/cppgenerator.h index 19041e3b0..8181e5711 100644 --- a/sources/shiboken6/generator/shiboken/cppgenerator.h +++ b/sources/shiboken6/generator/shiboken/cppgenerator.h @@ -32,6 +32,7 @@ #include "shibokengenerator.h" #include "abstractmetalang_enums.h" +#include <QtCore/QFlags> #include <QtCore/QSharedPointer> class OverloadDataNode; @@ -43,6 +44,11 @@ class OverloadDataRootNode; class CppGenerator : public ShibokenGenerator { public: + enum class PythonToCppTypeConversionFlag { + DisableOpaqueContainers = 0x1 + }; + Q_DECLARE_FLAGS(PythonToCppTypeConversionFlags, PythonToCppTypeConversionFlag) + CppGenerator(); const char *name() const override { return "Source generator"; } @@ -92,6 +98,19 @@ private: void writeContainerConverterFunctions(TextStream &s, const AbstractMetaType &containerType) const; + struct OpaqueContainerData + { + QString name; + QString checkFunctionName; + QString converterCheckFunctionName; + QString pythonToConverterFunctionName; + QString registrationCode; + }; + + OpaqueContainerData + writeOpaqueContainerConverterFunctions(TextStream &s, + const AbstractMetaType &containerType) const; + void writeSmartPointerConverterFunctions(TextStream &s, const AbstractMetaType &smartPointerType) const; @@ -176,7 +195,8 @@ private: const QString &pyIn, const QString &cppOut, const AbstractMetaClass *context = nullptr, - const QString &defaultValue = QString()) const; + const QString &defaultValue = {}, + PythonToCppTypeConversionFlags = {}) const; /// Writes the conversion rule for arguments of regular and virtual methods. void writeConversionRule(TextStream &s, const AbstractMetaFunctionCPtr &func, @@ -386,7 +406,7 @@ private: const CustomConversion *customConversion); static void writeEnumConverterInitialization(TextStream &s, const TypeEntry *enumType); static void writeEnumConverterInitialization(TextStream &s, const AbstractMetaEnum &metaEnum); - void writeContainerConverterInitialization(TextStream &s, const AbstractMetaType &type) const; + QString writeContainerConverterInitialization(TextStream &s, const AbstractMetaType &type) const; void writeSmartPointerConverterInitialization(TextStream &s, const AbstractMetaType &ype) const; static void writeExtendedConverterInitialization(TextStream &s, const TypeEntry *externalType, const AbstractMetaClassCList &conversions); @@ -467,4 +487,6 @@ private: }; }; +Q_DECLARE_OPERATORS_FOR_FLAGS(CppGenerator::PythonToCppTypeConversionFlags) + #endif // CPPGENERATOR_H diff --git a/sources/shiboken6/generator/shiboken/cppgenerator_container.cpp b/sources/shiboken6/generator/shiboken/cppgenerator_container.cpp new file mode 100644 index 000000000..cae2271d6 --- /dev/null +++ b/sources/shiboken6/generator/shiboken/cppgenerator_container.cpp @@ -0,0 +1,229 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt for Python. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "cppgenerator.h" +#include <abstractmetalang.h> +#include "apiextractorresult.h" +#include "ctypenames.h" +#include "textstream.h" + +#include <QtCore/QDebug> + +#include <algorithm> + +// Write a PyMethodDef entry, allowing for registering C++ functions +// under different names for Python. +static void writeMethod(TextStream &s, const QString &privateObjType, + const char *cppName, const char *pythonName, + const char *flags) +{ + if (pythonName == nullptr) + pythonName = cppName; + s << "{\"" << pythonName << "\", reinterpret_cast<PyCFunction>(" + << privateObjType << "::" << cppName << "), "<< flags + << ", \"" << /* doc */ pythonName << "\"},\n"; +} + +static inline void writeMethod(TextStream &s, const QString &privateObjType, + const char *cppName, const char *pythonName = nullptr) +{ + writeMethod(s, privateObjType, cppName, pythonName, "METH_O"); +} + +static inline void writeNoArgsMethod(TextStream &s, const QString &privateObjType, + const char *cppName, const char *pythonName = nullptr) +{ + writeMethod(s, privateObjType, cppName, pythonName, "METH_NOARGS"); +} + +static void writeSlot(TextStream &s, const char *tpName, const char *value) +{ + s << '{' << tpName << ", reinterpret_cast<void *>(" << value << ")},\n"; +} + +static void writeSlot(TextStream &s, const QString &privateObjType, + const char *tpName, const char *methodName) +{ + s << '{' << tpName << ", reinterpret_cast<void *>(" << privateObjType + << "::" << methodName << ")},\n"; +} + +// Generate code for a type wrapping a C++ container instantiation +CppGenerator::OpaqueContainerData + CppGenerator::writeOpaqueContainerConverterFunctions(TextStream &s, + const AbstractMetaType &containerType) const +{ + OpaqueContainerData result; + const auto &valueType = containerType.instantiations().constFirst(); + const auto *containerTypeEntry = static_cast<const ContainerTypeEntry *>(containerType.typeEntry()); + result.name = containerTypeEntry->opaqueContainerName(valueType.typeEntry()->name()); + + const auto cppSignature = containerType.cppSignature(); + s << "\n// Binding for " << cppSignature << "\n\n"; + + // Generate template specialization of value converter helper unless it is already there + const QString pyArg = u"pyArg"_qs; + const QString cppArg = u"cppArg"_qs; + + const QString valueTypeName = valueType.cppSignature(); + const QString checkFunction = cpythonCheckFunction(valueType); + + s << "template <>\nstruct ShibokenContainerValueConverter<" + << valueTypeName << ">\n{\n"; + // Type check + s << indent << "static bool checkValue(PyObject *" << pyArg << ")\n{\n" + << indent << "return " << checkFunction; + if (!checkFunction.contains(u'(')) + s << '('; + s << pyArg << ");\n" + << outdent << "}\n\n"; + + // C++ to Python + const bool passByConstRef = valueType.indirectionsV().isEmpty() + && !valueType.isCppPrimitive(); + s << "static PyObject *convertValueToPython("; + if (passByConstRef) + s << "const "; + s << valueTypeName << ' '; + if (passByConstRef) + s << '&'; + s << cppArg << ")\n{\n" << indent << "return "; + writeToPythonConversion(s, valueType, nullptr, cppArg); + s << ";\n" << outdent << "}\n\n"; + + // Python to C++ + s << "static std::optional<" << valueTypeName << "> convertValueToCpp(PyObject *" + << pyArg << ")\n{\n" << indent; + s << PYTHON_TO_CPPCONVERSION_STRUCT << ' ' << PYTHON_TO_CPP_VAR << ";\n" + << "if (!("; + writeTypeCheck(s, valueType, pyArg), isNumber(valueType.typeEntry()); + s << ")) {\n" << indent + << "PyErr_SetString(PyExc_TypeError, \"Wrong type passed to container conversion.\");\n" + << "return {};\n" << outdent << "}\n"; + writePythonToCppTypeConversion(s, valueType, pyArg, cppArg, nullptr, {}); + s << "return " << cppArg << ";\n" << outdent << "}\n" << outdent << "};\n\n"; + + const QString privateObjType = u"ShibokenSequenceContainerPrivate<"_qs + + cppSignature + u'>'; + + // methods + const bool isStdVector = containerType.name() == u"std::vector"; + const QString methods = result.name + u"_methods"_qs; + s << "static PyMethodDef " << methods << "[] = {\n" << indent; + writeMethod(s, privateObjType, "push_back"); + writeMethod(s, privateObjType, "push_back", "append"); // Qt convention + writeNoArgsMethod(s, privateObjType, "clear"); + writeNoArgsMethod(s, privateObjType, "pop_back"); + writeNoArgsMethod(s, privateObjType, "pop_back", "removeLast"); // Qt convention + if (!isStdVector) { + writeMethod(s, privateObjType, "push_front"); + writeMethod(s, privateObjType, "push_front", "prepend"); // Qt convention + writeNoArgsMethod(s, privateObjType, "pop_front"); + writeMethod(s, privateObjType, "pop_front", "removeFirst"); // Qt convention + } + s << "{nullptr, nullptr, 0, nullptr} // Sentinel\n" + << outdent << "};\n\n"; + + // slots + const QString slotsList = result.name + u"_slots"_qs; + s << "static PyType_Slot " << slotsList << "[] = {\n" << indent; + writeSlot(s, privateObjType, "Py_tp_init", "tpInit"); + writeSlot(s, privateObjType, "Py_tp_new", "tpNew"); + writeSlot(s, privateObjType, "Py_tp_free", "tpFree"); + writeSlot(s, "Py_tp_dealloc", "Sbk_object_dealloc"); // FIXME? + writeSlot(s, "Py_tp_methods", methods.toUtf8().constData()); + writeSlot(s, privateObjType, "Py_sq_ass_item", "sqSetItem"); + writeSlot(s, privateObjType, "Py_sq_length", "sqLen"); + writeSlot(s, privateObjType, "Py_sq_item", "sqGetItem"); + s << "{0, nullptr}\n" << outdent << "};\n\n"; + + // spec + const QString specName = result.name + u"_spec"_qs; + const QString name = moduleName() + u'.' + result.name; + s << "static PyType_Spec " << specName << " = {\n" << indent + << "\"" << name.count(u'.') << ':' << name << "\",\n" + << "sizeof(ShibokenContainer),\n0,\nPy_TPFLAGS_DEFAULT,\n" + << slotsList << outdent << "\n};\n\n"; + + // type creation function that sets a key in the type dict. + const QString typeCreationFName = u"create"_qs + result.name + u"Type"_qs; + s << "static inline PyTypeObject *" << typeCreationFName << "()\n{\n" << indent + << "auto *result = reinterpret_cast<PyTypeObject *>(SbkType_FromSpec(&" + << specName << "));\nPy_INCREF(Py_True);\n" + << "PyDict_SetItem(result->tp_dict, " + "Shiboken::PyMagicName::opaque_container(), Py_True);\n" + << "return result;\n" << outdent << "}\n\n"; + + // typeF() function + const QString typeFName = result.name + u"_TypeF"_qs; + s << "static PyTypeObject *" << typeFName << "()\n{\n" << indent + << "static PyTypeObject *type = " << typeCreationFName + << "();\nreturn type;\n" << outdent << "}\n\n"; + + // creation function from C++ reference, used by field accessors + // which are within extern "C" + const QString creationFunctionName = u"create"_qs + result.name; + s << "extern \"C\" PyObject *" << creationFunctionName + << '(' << containerType.cppSignature() << "*ct)\n{\n" << indent + << "auto *container = PyObject_New(ShibokenContainer, " << typeFName << "());\n" + << "auto *d = new ShibokenSequenceContainerPrivate<" + << containerType.cppSignature() << ">();\n" + << "d->m_list = ct;\ncontainer->d = d;\n" + << "return reinterpret_cast<PyObject *>(container);\n" << outdent + << "}\n\n"; + + // Check function + result.checkFunctionName = result.name + u"_Check"_qs; + s << "extern \"C\" int " << result.checkFunctionName << "(PyObject *" << pyArg + << ")\n{\n" << indent << "return " << pyArg << " != nullptr && " + << pyArg << " != Py_None && " << pyArg << "->ob_type == " + << typeFName << "();\n" << outdent << "}\n\n"; + + // SBK converter Python to C++ + result.pythonToConverterFunctionName = u"PythonToCpp"_qs + result.name; + s << "extern \"C\" void " << result.pythonToConverterFunctionName + << "(PyObject *" << pyArg << ", void *cppOut)\n{\n" << indent + << "auto *d = ShibokenSequenceContainerPrivate<" << cppSignature + << ">::get(" << pyArg << ");\n" + << "*reinterpret_cast<" << cppSignature << "**>(cppOut) = d->m_list;\n" + << outdent << "}\n\n"; + + // SBK check function for converting Python to C++ that returns the converter + result.converterCheckFunctionName = u"is"_qs + result.name + u"PythonToCppConvertible"_qs; + s << "extern \"C\" PythonToCppFunc " << result.converterCheckFunctionName + << "(PyObject *" << pyArg << ")\n{\n" << indent << "if (" + << result.checkFunctionName << '(' << pyArg << "))\n" << indent + << "return " << result.pythonToConverterFunctionName << ";\n" + << outdent << "return {};\n" << outdent << "}\n\n"; + + QTextStream(&result.registrationCode) << "ob_type = reinterpret_cast<PyObject *>(" + << typeFName + << "());\nPy_XINCREF(ob_type);\nPyModule_AddObject(module, \"" + << result.name << "\", ob_type);\n"; + return result; +} diff --git a/sources/shiboken6/generator/shiboken/shibokengenerator.cpp b/sources/shiboken6/generator/shiboken/shibokengenerator.cpp index e7dd58c58..282d8dc34 100644 --- a/sources/shiboken6/generator/shiboken/shibokengenerator.cpp +++ b/sources/shiboken6/generator/shiboken/shibokengenerator.cpp @@ -1150,6 +1150,11 @@ QString ShibokenGenerator::cpythonIsConvertibleFunction(AbstractMetaType metaTyp } QString result = QLatin1String("Shiboken::Conversions::"); + if (metaType.generateOpaqueContainer()) { + result += u"pythonToCppReferenceConversion("_qs + + converterObject(metaType) + u", "_qs; + return result; + } if (metaType.isWrapperType()) { if (metaType.isPointer() || metaType.isValueTypeWithCopyConstructorOnly()) result += u"pythonToCppPointerConversion"_qs; diff --git a/sources/shiboken6/libshiboken/CMakeLists.txt b/sources/shiboken6/libshiboken/CMakeLists.txt index 3e1b14594..da1b5623e 100644 --- a/sources/shiboken6/libshiboken/CMakeLists.txt +++ b/sources/shiboken6/libshiboken/CMakeLists.txt @@ -48,6 +48,7 @@ debugfreehook.cpp gilstate.cpp helper.cpp sbkarrayconverter.cpp +sbkcontainer.cpp sbkconverter.cpp sbkenum.cpp sbkfeature_base.cpp @@ -130,6 +131,7 @@ install(FILES gilstate.h helper.h sbkarrayconverter.h + sbkcontainer.h sbkconverter.h sbkenum.h sbkenum_p.h diff --git a/sources/shiboken6/libshiboken/sbkcontainer.cpp b/sources/shiboken6/libshiboken/sbkcontainer.cpp new file mode 100644 index 000000000..98fd700aa --- /dev/null +++ b/sources/shiboken6/libshiboken/sbkcontainer.cpp @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt for Python. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "sbkcontainer.h" +#include "sbkstaticstrings.h" + +namespace Shiboken +{ +bool isOpaqueContainer(PyObject *o) +{ + return o != nullptr && o != Py_None + && PyDict_Contains(o->ob_type->tp_dict, + Shiboken::PyMagicName::opaque_container()) == 1; + +} +} // Shiboken diff --git a/sources/shiboken6/libshiboken/sbkcontainer.h b/sources/shiboken6/libshiboken/sbkcontainer.h new file mode 100644 index 000000000..284bf8c5c --- /dev/null +++ b/sources/shiboken6/libshiboken/sbkcontainer.h @@ -0,0 +1,201 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt for Python. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef SBK_CONTAINER_H +#define SBK_CONTAINER_H + +#include "sbkpython.h" +#include "shibokenmacros.h" + +#include <algorithm> +#include <iterator> +#include <optional> + +extern "C" +{ +struct LIBSHIBOKEN_API ShibokenContainer +{ + PyObject_HEAD + void *d; +}; + +} // extern "C" + +// Conversion helper traits for container values (Keep it out of namespace as +// otherwise clashes occur). +template <class Value> +struct ShibokenContainerValueConverter +{ + static bool checkValue(PyObject *pyArg); + static PyObject *convertValueToPython(Value v); + static std::optional<Value> convertValueToCpp(PyObject pyArg); +}; + +template <class SequenceContainer> +class ShibokenSequenceContainerPrivate // Helper for sequence type containers +{ +public: + using value_type = typename SequenceContainer::value_type; + using OptionalValue = typename std::optional<value_type>; + + SequenceContainer *m_list{}; + bool m_ownsList = false; + + static PyObject *tpNew(PyTypeObject *subtype, PyObject * /* args */, PyObject * /* kwds */) + { + auto *me = reinterpret_cast<ShibokenContainer *>(subtype->tp_alloc(subtype, 0)); + auto *d = new ShibokenSequenceContainerPrivate; + d->m_list = new SequenceContainer; + d->m_ownsList = true; + me->d = d; + return reinterpret_cast<PyObject *>(me); + } + + static int tpInit(PyObject * /* self */, PyObject * /* args */, PyObject * /* kwds */) + { + return 0; + } + + static void tpFree(void *self) + { + auto *pySelf = reinterpret_cast<PyObject *>(self); + auto *d = get(pySelf); + if (d->m_ownsList) + delete d->m_list; + delete d; + Py_TYPE(pySelf)->tp_base->tp_free(self); + } + + static Py_ssize_t sqLen(PyObject *self) + { + return get(self)->m_list->size(); + } + + static PyObject *sqGetItem(PyObject *self, Py_ssize_t i) + { + auto *d = get(self); + if (i < 0 || i >= Py_ssize_t(d->m_list->size())) { + PyErr_SetString(PyExc_IndexError, "index out of bounds"); + return nullptr; + } + auto it = d->m_list->cbegin(); + std::advance(it, i); + return ShibokenContainerValueConverter<value_type>::convertValueToPython(*it); + } + + static int sqSetItem(PyObject *self, Py_ssize_t i, PyObject *pyArg) + { + auto *d = get(self); + if (i < 0 || i >= Py_ssize_t(d->m_list->size())) { + PyErr_SetString(PyExc_IndexError, "index out of bounds"); + return -1; + } + auto it = d->m_list->begin(); + std::advance(it, i); + OptionalValue value = ShibokenContainerValueConverter<value_type>::convertValueToCpp(pyArg); + if (!value.has_value()) + return -1; + *it = value.value(); + return 0; + } + + static PyObject *push_back(PyObject *self, PyObject *pyArg) + { + if (!ShibokenContainerValueConverter<value_type>::checkValue(pyArg)) { + PyErr_SetString(PyExc_TypeError, "wrong type passed to append."); + return nullptr; + } + + auto *d = get(self); + OptionalValue value = ShibokenContainerValueConverter<value_type>::convertValueToCpp(pyArg); + if (!value.has_value()) + return nullptr; + d->m_list->push_back(value.value()); + Py_RETURN_NONE; + } + + static PyObject *push_front(PyObject *self, PyObject *pyArg) + { + if (!ShibokenContainerValueConverter<value_type>::checkValue(pyArg)) { + PyErr_SetString(PyExc_TypeError, "wrong type passed to append."); + return nullptr; + } + + auto *d = get(self); + OptionalValue value = ShibokenContainerValueConverter<value_type>::convertValueToCpp(pyArg); + if (!value.has_value()) + return nullptr; + d->m_list->push_front(value.value()); + Py_RETURN_NONE; + } + + static PyObject *clear(PyObject *self) + { + auto *d = get(self); + d->m_list->clear(); + Py_RETURN_NONE; + } + + static PyObject *pop_back(PyObject *self) + { + auto *d = get(self); + d->m_list->pop_back(); + Py_RETURN_NONE; + } + + static PyObject *pop_front(PyObject *self) + { + auto *d = get(self); + d->m_list->pop_front(); + Py_RETURN_NONE; + } + + static ShibokenSequenceContainerPrivate *get(PyObject *self) + { + auto *data = reinterpret_cast<ShibokenContainer *>(self); + return reinterpret_cast<ShibokenSequenceContainerPrivate *>(data->d); + } +}; + +namespace Shiboken +{ +LIBSHIBOKEN_API bool isOpaqueContainer(PyObject *o); +} + +#endif // SBK_CONTAINER_H diff --git a/sources/shiboken6/libshiboken/sbkstaticstrings.cpp b/sources/shiboken6/libshiboken/sbkstaticstrings.cpp index 7b2ddfb3e..20aa1b8a0 100644 --- a/sources/shiboken6/libshiboken/sbkstaticstrings.cpp +++ b/sources/shiboken6/libshiboken/sbkstaticstrings.cpp @@ -111,5 +111,6 @@ STATIC_STRING_IMPL(new_, "__new__") STATIC_STRING_IMPL(objclass, "__objclass__") STATIC_STRING_IMPL(signature, "__signature__") STATIC_STRING_IMPL(weakrefoffset, "__weakrefoffset__") +STATIC_STRING_IMPL(opaque_container, "__opaque_container__") } // namespace PyMagicName } // namespace Shiboken diff --git a/sources/shiboken6/libshiboken/sbkstaticstrings.h b/sources/shiboken6/libshiboken/sbkstaticstrings.h index 773ff4e6c..fd0c06e43 100644 --- a/sources/shiboken6/libshiboken/sbkstaticstrings.h +++ b/sources/shiboken6/libshiboken/sbkstaticstrings.h @@ -81,6 +81,7 @@ LIBSHIBOKEN_API PyObject *name(); LIBSHIBOKEN_API PyObject *property_methods(); LIBSHIBOKEN_API PyObject *qualname(); LIBSHIBOKEN_API PyObject *self(); +LIBSHIBOKEN_API PyObject *opaque_container(); } // namespace PyMagicName } // namespace Shiboken diff --git a/sources/shiboken6/tests/libminimal/listuser.cpp b/sources/shiboken6/tests/libminimal/listuser.cpp index 0d7721c3c..402696acd 100644 --- a/sources/shiboken6/tests/libminimal/listuser.cpp +++ b/sources/shiboken6/tests/libminimal/listuser.cpp @@ -119,3 +119,7 @@ ListUser::sumListOfIntLists(std::list<std::list<int> > intListList) return total; } +void ListUser::setStdIntList(const std::list<int> &l) +{ + m_stdIntList = l; +} diff --git a/sources/shiboken6/tests/libminimal/listuser.h b/sources/shiboken6/tests/libminimal/listuser.h index 6c7e6aedd..31c4efbd1 100644 --- a/sources/shiboken6/tests/libminimal/listuser.h +++ b/sources/shiboken6/tests/libminimal/listuser.h @@ -69,6 +69,10 @@ struct LIBMINIMAL_API ListUser std::list<std::list<int> > callCreateListOfIntLists(int num) { return createListOfIntLists(num); } virtual int sumListOfIntLists(std::list<std::list<int> > intListList); int callSumListOfIntLists(std::list<std::list<int> > intListList) { return sumListOfIntLists(intListList); } + + void setStdIntList(const std::list<int> &l); + + std::list<int> m_stdIntList; }; #endif // LISTUSER_H diff --git a/sources/shiboken6/tests/minimalbinding/listuser_test.py b/sources/shiboken6/tests/minimalbinding/listuser_test.py index b5048aa44..1a11ccfbf 100644 --- a/sources/shiboken6/tests/minimalbinding/listuser_test.py +++ b/sources/shiboken6/tests/minimalbinding/listuser_test.py @@ -39,7 +39,7 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from shiboken_paths import init_paths init_paths() -from minimal import ListUser, Val, Obj +from minimal import ListUser, Val, Obj, StdIntList class ExtListUser(ListUser): @@ -321,6 +321,30 @@ class ListOfIntListConversionTest(unittest.TestCase): self.assertEqual(lu.sumListOfIntLists(lst), sum([sum(line) for line in [range(4)] * 4]) * 2) self.assertEqual(lu.callSumListOfIntLists(lst), sum([sum(line) for line in [range(4)] * 4]) * 2) + def testOpaqueContainer(self): + lu = ListUser() + + # Set via Python + python_list = [1,2] + lu.setStdIntList(python_list) + self.assertEqual(len(lu.m_stdIntList), 2) + self.assertEqual(lu.m_stdIntList[0], 1) + self.assertEqual(lu.m_stdIntList[1], 2) + + # Set via C++ + cpp_list = StdIntList() + cpp_list.append(3) + cpp_list.append(4) + lu.setStdIntList(cpp_list) + self.assertEqual(len(lu.m_stdIntList), 2) + self.assertEqual(lu.m_stdIntList[0], 3) + self.assertEqual(lu.m_stdIntList[1], 4) + + # Access field directly via reference + lu.m_stdIntList.append(5) + self.assertEqual(len(lu.m_stdIntList), 3) + self.assertEqual(lu.m_stdIntList[2], 5) + if __name__ == '__main__': unittest.main() diff --git a/sources/shiboken6/tests/minimalbinding/typesystem_minimal.xml b/sources/shiboken6/tests/minimalbinding/typesystem_minimal.xml index 625615fa1..2b9cbc89f 100644 --- a/sources/shiboken6/tests/minimalbinding/typesystem_minimal.xml +++ b/sources/shiboken6/tests/minimalbinding/typesystem_minimal.xml @@ -17,7 +17,8 @@ </conversion-rule> </primitive-type> - <container-type name="std::list" type="list"> + <container-type name="std::list" type="list" + opaque-containers="int:StdIntList"> <include file-name="list" location="global"/> <conversion-rule> <native-to-target> @@ -47,7 +48,9 @@ <value-type name="Val"> <enum-type name="ValEnum"/> </value-type> - <value-type name="ListUser"/> + <value-type name="ListUser"> + <modify-field name="m_stdIntList" opaque-container="yes"/> + </value-type> <value-type name="MinBoolUser"/> <container-type name="std::vector" type="vector"> |