diff options
| author | Friedemann Kleint <Friedemann.Kleint@qt.io> | 2022-05-17 20:34:37 +0200 |
|---|---|---|
| committer | Friedemann Kleint <Friedemann.Kleint@qt.io> | 2022-05-27 16:49:08 +0200 |
| commit | b815111f8aefd89e01b966ecc86a8c77220d7e99 (patch) | |
| tree | 40c31010fc700b5279e1d3f860a4984a22cb48b5 | |
| parent | bcd1ac22f8e4e804b4082e311d4a8c43f3f3d4d6 (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>
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*"> |
