aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFriedemann Kleint <Friedemann.Kleint@qt.io>2022-05-17 20:34:37 +0200
committerFriedemann Kleint <Friedemann.Kleint@qt.io>2022-05-27 16:49:08 +0200
commitb815111f8aefd89e01b966ecc86a8c77220d7e99 (patch)
tree40c31010fc700b5279e1d3f860a4984a22cb48b5
parentbcd1ac22f8e4e804b4082e311d4a8c43f3f3d4d6 (diff)
shiboken6: Add a way to specify free functions
Provide a way to add completely custom slots to the PyMethodDefs. For example, this allows handling writing variadic argument lists bypassing the overload decisor. Task-number: PYSIDE-1202 Task-number: PYSIDE-1905 Change-Id: Id8686e68e4c410dabbefb633b496c134deaab5ca Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Christian Tismer <tismer@stackless.com>
-rw-r--r--sources/shiboken6/ApiExtractor/complextypeentry.h9
-rw-r--r--sources/shiboken6/ApiExtractor/typesystem.cpp13
-rw-r--r--sources/shiboken6/ApiExtractor/typesystemparser.cpp45
-rw-r--r--sources/shiboken6/ApiExtractor/typesystemparser_p.h3
-rw-r--r--sources/shiboken6/doc/typesystem_manipulating_objects.rst32
-rw-r--r--sources/shiboken6/doc/typesystem_specifying_types.rst4
-rw-r--r--sources/shiboken6/generator/shiboken/cppgenerator.cpp16
-rw-r--r--sources/shiboken6/tests/samplebinding/intwrapper_test.py5
-rw-r--r--sources/shiboken6/tests/samplebinding/samplesnippets.cpp20
-rw-r--r--sources/shiboken6/tests/samplebinding/typesystem_sample.xml7
10 files changed, 151 insertions, 3 deletions
diff --git a/sources/shiboken6/ApiExtractor/complextypeentry.h b/sources/shiboken6/ApiExtractor/complextypeentry.h
index f70038d4e..a7f10dd51 100644
--- a/sources/shiboken6/ApiExtractor/complextypeentry.h
+++ b/sources/shiboken6/ApiExtractor/complextypeentry.h
@@ -5,11 +5,17 @@
#define COMPLEXTYPEENTRY_H
#include "typesystem.h"
+#include "pymethoddefentry.h"
#include <QtCore/QSet>
class ComplexTypeEntryPrivate;
+struct TypeSystemPyMethodDefEntry : public PyMethodDefEntry
+{
+ QStringList signatures;
+};
+
struct TypeSystemProperty
{
bool isValid() const { return !name.isEmpty() && !read.isEmpty() && !type.isEmpty(); }
@@ -64,6 +70,9 @@ public:
void setAddedFunctions(const AddedFunctionList &addedFunctions);
void addNewFunction(const AddedFunctionPtr &addedFunction);
+ const QList<TypeSystemPyMethodDefEntry> &addedPyMethodDefEntrys() const;
+ void addPyMethodDef(const TypeSystemPyMethodDefEntry &p);
+
// Functions specified in the "generate-functions" attribute
const QSet<QString> &generateFunctions() const;
void setGenerateFunctions(const QSet<QString> &f);
diff --git a/sources/shiboken6/ApiExtractor/typesystem.cpp b/sources/shiboken6/ApiExtractor/typesystem.cpp
index ee709542a..f2c57def5 100644
--- a/sources/shiboken6/ApiExtractor/typesystem.cpp
+++ b/sources/shiboken6/ApiExtractor/typesystem.cpp
@@ -1231,6 +1231,7 @@ public:
QSet<QString> m_generateFunctions;
FieldModificationList m_fieldMods;
QList<TypeSystemProperty> m_properties;
+ QList<TypeSystemPyMethodDefEntry> m_PyMethodDefEntrys;
QString m_defaultConstructor;
QString m_defaultSuperclass;
QString m_qualifiedCppName;
@@ -1354,6 +1355,18 @@ void ComplexTypeEntry::addNewFunction(const AddedFunctionPtr &addedFunction)
d->m_addedFunctions << addedFunction;
}
+const QList<TypeSystemPyMethodDefEntry> &ComplexTypeEntry::addedPyMethodDefEntrys() const
+{
+ S_D(const ComplexTypeEntry);
+ return d->m_PyMethodDefEntrys;
+}
+
+void ComplexTypeEntry::addPyMethodDef(const TypeSystemPyMethodDefEntry &p)
+{
+ S_D(ComplexTypeEntry);
+ d->m_PyMethodDefEntrys.append(p);
+}
+
const QSet<QString> &ComplexTypeEntry::generateFunctions() const
{
S_D(const ComplexTypeEntry);
diff --git a/sources/shiboken6/ApiExtractor/typesystemparser.cpp b/sources/shiboken6/ApiExtractor/typesystemparser.cpp
index 71ae1b62b..c0b387857 100644
--- a/sources/shiboken6/ApiExtractor/typesystemparser.cpp
+++ b/sources/shiboken6/ApiExtractor/typesystemparser.cpp
@@ -427,6 +427,7 @@ static const StackElementHash &stackElementHash()
static const StackElementHash result{
{u"add-conversion", StackElement::AddConversion},
{u"add-function", StackElement::AddFunction},
+ {u"add-pymethoddef", StackElement::AddPyMethodDef},
{u"array", StackElement::Array},
{u"container-type", StackElement::ContainerTypeEntry},
{u"conversion-rule", StackElement::ConversionRule},
@@ -2502,6 +2503,46 @@ bool TypeSystemParser::parseAddFunction(const ConditionalStreamReader &,
return true;
}
+bool TypeSystemParser::parseAddPyMethodDef(const ConditionalStreamReader &,
+ StackElement topElement,
+ QXmlStreamAttributes *attributes)
+{
+ if (!isComplexTypeEntry(topElement)) {
+ m_error = u"add-pymethoddef requires a complex type as parent, was="_s
+ + tagFromElement(topElement).toString();
+ return false;
+ }
+
+ TypeSystemPyMethodDefEntry def;
+ for (int i = attributes->size() - 1; i >= 0; --i) {
+ const auto name = attributes->at(i).qualifiedName();
+ if (name == nameAttribute()) {
+ def.name = attributes->takeAt(i).value().toString();
+ } else if (name == u"doc") {
+ def.doc = attributes->takeAt(i).value().toString();
+ } else if (name == u"function") {
+ def.function = attributes->takeAt(i).value().toString();
+ } else if (name == u"flags") {
+ auto attribute = attributes->takeAt(i);
+ const auto flags = attribute.value().split(u'|', Qt::SkipEmptyParts);
+ for (const auto &flag : flags)
+ def.methFlags.append(flag.toString().toUtf8());
+ } else if (name == u"signatures") {
+ auto attribute = attributes->takeAt(i);
+ const auto signatures = attribute.value().split(u';', Qt::SkipEmptyParts);
+ for (const auto &signature : signatures)
+ def.signatures.append(signature.toString());
+ }
+ }
+
+ if (def.name.isEmpty() || def.function.isEmpty()) {
+ m_error = u"add-pymethoddef requires at least a name and a function attribute"_s;
+ return false;
+ }
+ static_cast<ComplexTypeEntry *>(m_contextStack.top()->entry)->addPyMethodDef(def);
+ return true;
+}
+
bool TypeSystemParser::parseProperty(const ConditionalStreamReader &, StackElement topElement,
QXmlStreamAttributes *attributes)
{
@@ -3300,6 +3341,10 @@ bool TypeSystemParser::startElement(const ConditionalStreamReader &reader, Stack
if (!parseAddFunction(reader, topElement, element, &attributes))
return false;
break;
+ case StackElement::AddPyMethodDef:
+ if (!parseAddPyMethodDef(reader, topElement, &attributes))
+ return false;
+ break;
case StackElement::Property:
if (!parseProperty(reader, topElement, &attributes))
return false;
diff --git a/sources/shiboken6/ApiExtractor/typesystemparser_p.h b/sources/shiboken6/ApiExtractor/typesystemparser_p.h
index 2e3d9d97e..c14a46c04 100644
--- a/sources/shiboken6/ApiExtractor/typesystemparser_p.h
+++ b/sources/shiboken6/ApiExtractor/typesystemparser_p.h
@@ -70,6 +70,7 @@ enum class StackElement {
InsertTemplate,
Replace,
AddFunction,
+ AddPyMethodDef,
DeclareFunction,
NativeToTarget,
TargetToNative,
@@ -221,6 +222,8 @@ private:
bool parseModifyField(const ConditionalStreamReader &, QXmlStreamAttributes *);
bool parseAddFunction(const ConditionalStreamReader &, StackElement topElement,
StackElement t, QXmlStreamAttributes *);
+ bool parseAddPyMethodDef(const ConditionalStreamReader &,
+ StackElement topElement, QXmlStreamAttributes *attributes);
bool parseProperty(const ConditionalStreamReader &, StackElement topElement,
QXmlStreamAttributes *);
bool parseModifyFunction(const ConditionalStreamReader &, StackElement topElement,
diff --git a/sources/shiboken6/doc/typesystem_manipulating_objects.rst b/sources/shiboken6/doc/typesystem_manipulating_objects.rst
index 5ad5f914f..88aa18390 100644
--- a/sources/shiboken6/doc/typesystem_manipulating_objects.rst
+++ b/sources/shiboken6/doc/typesystem_manipulating_objects.rst
@@ -346,6 +346,38 @@ declare-function
This tells shiboken a public function of that signature exists and
bindings will be created in specializations of ``QList``.
+
+.. _add-pymethoddef:
+
+add-pymethoddef
+^^^^^^^^^^^^^^^
+
+The ``add-pymethoddef`` element allows you to add a free function to
+the ``PyMethodDef`` array of the type. No argument or result conversion
+is generated, allowing for variadic functions and more flexible
+arguments checking.
+
+.. code-block:: xml
+
+ <add-pymethoddef name="..." function="..." flags="..." doc="..."
+ signatures="..."/>
+
+The ``name`` attribute specifies the name.
+
+The ``function`` attribute specifies the implementation (a static function
+of type ``PyCFunction``).
+
+The ``flags`` attribute specifies the flags (typically ``METH_VARARGS``,
+see `Common Object Structures`_).
+
+The optional ``doc`` attribute specifies the documentation to be set to the
+``ml_doc`` field.
+
+The optional ``signatures`` attribute specifies a semicolon-separated list
+of signatures of the function.
+
+.. _Common Object Structures: https://docs.python.org/3/c-api/structures.html
+
.. _property-declare:
property
diff --git a/sources/shiboken6/doc/typesystem_specifying_types.rst b/sources/shiboken6/doc/typesystem_specifying_types.rst
index e1500d117..60e84fe11 100644
--- a/sources/shiboken6/doc/typesystem_specifying_types.rst
+++ b/sources/shiboken6/doc/typesystem_specifying_types.rst
@@ -306,7 +306,7 @@ value-type
The ``value-type`` node indicates that the given C++ type is mapped onto the target
language as a value type. This means that it is an object passed by value on C++,
i.e. it is stored in the function call stack. It is a child of the :ref:`typesystem`
- node or other type nodes and may contain :ref:`add-function`,
+ node or other type nodes and may contain :ref:`add-function`, :ref:`add-pymethoddef`,
:ref:`declare-function`, :ref:`conversion-rule`, :ref:`enum-type`,
:ref:`extra-includes`, :ref:`modify-function`, :ref:`object-type`,
:ref:`smart-pointer-type`, :ref:`typedef-type` or further ``value-type``
@@ -385,7 +385,7 @@ object-type
The object-type node indicates that the given C++ type is mapped onto the target
language as an object type. This means that it is an object passed by pointer on
C++ and it is stored on the heap. It is a child of the :ref:`typesystem` node
- or other type nodes and may contain :ref:`add-function`,
+ or other type nodes and may contain :ref:`add-function`, :ref:`add-pymethoddef`,
:ref:`declare-function`, :ref:`enum-type`, :ref:`extra-includes`,
:ref:`modify-function`, ``object-type``, :ref:`smart-pointer-type`,
:ref:`typedef-type` or :ref:`value-type` child nodes.
diff --git a/sources/shiboken6/generator/shiboken/cppgenerator.cpp b/sources/shiboken6/generator/shiboken/cppgenerator.cpp
index fa91fc8d1..e75b93215 100644
--- a/sources/shiboken6/generator/shiboken/cppgenerator.cpp
+++ b/sources/shiboken6/generator/shiboken/cppgenerator.cpp
@@ -494,6 +494,19 @@ static bool needsTypeDiscoveryFunction(const AbstractMetaClass *c)
&& (c->isPolymorphic() || !c->typeEntry()->polymorphicIdValue().isEmpty());
}
+static void writeAddedTypeSignatures(TextStream &s, const ComplexTypeEntry *te)
+{
+ for (const auto &e : te->addedPyMethodDefEntrys()) {
+ if (auto count = e.signatures.size()) {
+ for (qsizetype i = 0; i < count; ++i) {
+ if (count > 1)
+ s << i << ':';
+ s << e.signatures.at(i) << '\n';
+ }
+ }
+ }
+}
+
/// Function used to write the class generated binding code on the buffer
/// \param s the output buffer
/// \param classContext the pointer to metaclass information
@@ -638,6 +651,8 @@ void CppGenerator::generateClass(TextStream &s, const GeneratorContext &classCon
md << defEntries;
}
}
+ for (const auto &pyMethodDef : typeEntry->addedPyMethodDefEntrys())
+ md << pyMethodDef << ",\n";
const QString methodsDefinitions = md.toString();
const QString singleMethodDefinitions = smd.toString();
@@ -787,6 +802,7 @@ void CppGenerator::generateClass(TextStream &s, const GeneratorContext &classCon
s << '\n';
writeConverterFunctions(s, metaClass, classContext);
+ writeAddedTypeSignatures(signatureStream, typeEntry);
writeClassRegister(s, metaClass, classContext, signatureStream);
if (metaClass->hasStaticFields())
diff --git a/sources/shiboken6/tests/samplebinding/intwrapper_test.py b/sources/shiboken6/tests/samplebinding/intwrapper_test.py
index 6dabfc850..21cab6b35 100644
--- a/sources/shiboken6/tests/samplebinding/intwrapper_test.py
+++ b/sources/shiboken6/tests/samplebinding/intwrapper_test.py
@@ -29,6 +29,11 @@ class IntWrapperTest(unittest.TestCase):
i -= ten2
self.assertTrue(i == ten1)
+ def testAddPyMethodDef(self):
+ """Test of added free function (PYSIDE-1905)."""
+ i = IntWrapper(10)
+ self.assertEqual(i.add_ints(10, 20), 30)
+
if __name__ == '__main__':
unittest.main()
diff --git a/sources/shiboken6/tests/samplebinding/samplesnippets.cpp b/sources/shiboken6/tests/samplebinding/samplesnippets.cpp
new file mode 100644
index 000000000..e71ba3737
--- /dev/null
+++ b/sources/shiboken6/tests/samplebinding/samplesnippets.cpp
@@ -0,0 +1,20 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+// @snippet intwrapper_add_ints
+extern "C" {
+static PyObject *Sbk_IntWrapper_add_ints(PyObject *self, PyObject *args)
+{
+ PyObject *result = nullptr;
+ if (PyTuple_Check(args) != 0 && PyTuple_Size(args) == 2) {
+ PyObject *arg1 = PyTuple_GetItem(args, 0);
+ PyObject *arg2 = PyTuple_GetItem(args, 1);
+ if (PyLong_Check(arg1) != 0 && PyLong_Check(arg2) != 0)
+ result = PyLong_FromLong(PyLong_AsLong(arg1) + PyLong_AsLong(arg2));
+ }
+ if (result == nullptr)
+ PyErr_SetString(PyExc_TypeError, "expecting 2 ints");
+ return result;
+}
+}
+// @snippet intwrapper_add_ints
diff --git a/sources/shiboken6/tests/samplebinding/typesystem_sample.xml b/sources/shiboken6/tests/samplebinding/typesystem_sample.xml
index 9a3288126..85d354398 100644
--- a/sources/shiboken6/tests/samplebinding/typesystem_sample.xml
+++ b/sources/shiboken6/tests/samplebinding/typesystem_sample.xml
@@ -1992,7 +1992,12 @@
<object-type name="Collector" stream="yes"/>
- <value-type name="IntWrapper" />
+ <value-type name="IntWrapper">
+ <inject-code class="native" position="beginning"
+ file="samplesnippets.cpp" snippet="intwrapper_add_ints"/>
+ <add-pymethoddef name="add_ints" function="Sbk_IntWrapper_add_ints"
+ flags="METH_VARARGS"/>
+ </value-type>
<value-type name="Str" hash-function="strHash">
<add-function signature="__str__" return-type="PyObject*">