aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFriedemann Kleint <Friedemann.Kleint@qt.io>2021-07-14 14:15:02 +0200
committerFriedemann Kleint <Friedemann.Kleint@qt.io>2021-09-24 13:52:35 +0200
commitbce1bfb3af99aeb24259df34d662e8fcf072d3fd (patch)
treeef36333a2b059f4278cc5aad4efa860f9e4cd30b
parent79b32f4d4b5154ba8001bafc481fb6edacc10280 (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>
-rw-r--r--examples/widgets/painting/plot/plot.py105
-rw-r--r--examples/widgets/painting/plot/plot.pyproject3
-rw-r--r--sources/pyside6/PySide6/QtCore/typesystem_core_common.xml3
-rw-r--r--sources/shiboken6/ApiExtractor/abstractmetafield.cpp10
-rw-r--r--sources/shiboken6/ApiExtractor/abstractmetafield.h1
-rw-r--r--sources/shiboken6/ApiExtractor/abstractmetatype.cpp33
-rw-r--r--sources/shiboken6/ApiExtractor/abstractmetatype.h5
-rw-r--r--sources/shiboken6/ApiExtractor/modifications.cpp12
-rw-r--r--sources/shiboken6/ApiExtractor/modifications.h3
-rw-r--r--sources/shiboken6/ApiExtractor/typesystem.cpp48
-rw-r--r--sources/shiboken6/ApiExtractor/typesystem.h12
-rw-r--r--sources/shiboken6/ApiExtractor/typesystemparser.cpp32
-rw-r--r--sources/shiboken6/doc/typesystem.rst1
-rw-r--r--sources/shiboken6/doc/typesystem_containers.rst45
-rw-r--r--sources/shiboken6/doc/typesystem_manipulating_objects.rst7
-rw-r--r--sources/shiboken6/doc/typesystem_specifying_types.rst7
-rw-r--r--sources/shiboken6/generator/CMakeLists.txt1
-rw-r--r--sources/shiboken6/generator/shiboken/cppgenerator.cpp66
-rw-r--r--sources/shiboken6/generator/shiboken/cppgenerator.h26
-rw-r--r--sources/shiboken6/generator/shiboken/cppgenerator_container.cpp229
-rw-r--r--sources/shiboken6/generator/shiboken/shibokengenerator.cpp5
-rw-r--r--sources/shiboken6/libshiboken/CMakeLists.txt2
-rw-r--r--sources/shiboken6/libshiboken/sbkcontainer.cpp52
-rw-r--r--sources/shiboken6/libshiboken/sbkcontainer.h201
-rw-r--r--sources/shiboken6/libshiboken/sbkstaticstrings.cpp1
-rw-r--r--sources/shiboken6/libshiboken/sbkstaticstrings.h1
-rw-r--r--sources/shiboken6/tests/libminimal/listuser.cpp4
-rw-r--r--sources/shiboken6/tests/libminimal/listuser.h4
-rw-r--r--sources/shiboken6/tests/minimalbinding/listuser_test.py26
-rw-r--r--sources/shiboken6/tests/minimalbinding/typesystem_minimal.xml7
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">