From 9eee97b5e66c9771e428539eb340b2c2a3756968 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simo=20F=C3=A4lt?= Date: Tue, 5 May 2020 09:04:13 +0300 Subject: Test with additional python version Change-Id: I17726f6bd02d36a7acc3ed99ca11796b780b837d Reviewed-by: Cristian Maureira-Fredes --- build_scripts/utils.py | 20 +++++++++++++++----- coin_test_instructions.py | 7 ++++++- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/build_scripts/utils.py b/build_scripts/utils.py index 9f6d472cc..b0c2f8899 100644 --- a/build_scripts/utils.py +++ b/build_scripts/utils.py @@ -1102,13 +1102,23 @@ def get_qtci_virtualEnv(python_ver, host, hostArch, targetArch): _pExe = "python.exe" # With windows we are creating building 32-bit target in 64-bit host if hostArch == "X86_64" and targetArch == "X86": - if python_ver == "3": - _pExe = os.path.join(os.getenv("PYTHON3_32_PATH"), "python.exe") + if python_ver.startswith("3"): + print("Try to find python from {} env variable".format("PYTHON"+python_ver+"-32_PATH")) + _path = os.getenv("PYTHON"+python_ver+"-32_PATH", "") + _pExe = os.path.join(_path, "python.exe") + if not os.path.isfile(_pExe): + print("Can't find python.exe from {}, using default python3".format(_pExe)) + _pExe = os.path.join(os.getenv("PYTHON3_32_PATH"), "python.exe") else: - _pExe = os.path.join(os.getenv("PYTHON2_32_PATH"), "python.exe") + _pExe = os.path.join(os.getenv("PYTHON2_32_PATH"), "python.exe") else: - if python_ver == "3": - _pExe = os.path.join(os.getenv("PYTHON3_PATH"), "python.exe") + if python_ver.startswith("3"): + print("Try to find python from {} env variable".format("PYTHON"+python_ver+"-64_PATH")) + _path = os.getenv("PYTHON"+python_ver+"-64_PATH", "") + _pExe = os.path.join(_path, "python.exe") + if not os.path.isfile(_pExe): + print("Can't find python.exe from {}, using default python3".format(_pExe)) + _pExe = os.path.join(os.getenv("PYTHON3_PATH"), "python.exe") env_python = _env + "\\Scripts\\python.exe" env_pip = _env + "\\Scripts\\pip.exe" else: diff --git a/coin_test_instructions.py b/coin_test_instructions.py index 16ba601f7..30d808e25 100644 --- a/coin_test_instructions.py +++ b/coin_test_instructions.py @@ -110,7 +110,12 @@ def run_test_instructions(): testRun =+ 1 # We know that second build was with python3 if CI_RELEASE_CONF: - call_testrunner("3", str(testRun)) + # In win machines, there are additional python versions to test with + if CI_HOST_OS == "Windows": + call_testrunner("3.6.1", str(testRun)) + call_testrunner("3.8.1", str(testRun)) + else: + call_testrunner("3", str(testRun)) if __name__ == "__main__": run_test_instructions() -- cgit v1.2.3 From 03b9069a7e408568e98cc826c0cb7ea2e5035f1c Mon Sep 17 00:00:00 2001 From: Christian Tismer Date: Wed, 23 Sep 2020 13:14:25 +0200 Subject: Signature: Revert SbkSpecial_Type_Ready to PyType_Ready The early signature module tried to minimize the visible changes to the code base. It replaced the `PyType_Ready` call by a special version which did other things as well. We replace that special call by a more intuitive function `InitSignatureStrings` that does exactly that and nothing more. The functionality of the module is unchanged. Change-Id: Ic2f9cd29b0352f0a24daa55b01420c77d103c0b2 Task-number: PYSIDE-510 Reviewed-by: Christian Tismer --- .../PySide2/QtQml/pysideqmlregistertype.cpp | 4 ++-- sources/pyside2/libpyside/pysideclassinfo.cpp | 2 +- sources/pyside2/libpyside/pysidemetafunction.cpp | 2 +- sources/pyside2/libpyside/pysideproperty.cpp | 2 +- sources/pyside2/libpyside/pysidesignal.cpp | 6 +++--- sources/pyside2/libpyside/pysideslot.cpp | 2 +- .../shiboken2/generator/shiboken2/cppgenerator.cpp | 22 ++++++++++------------ sources/shiboken2/libshiboken/basewrapper.cpp | 7 +------ sources/shiboken2/libshiboken/basewrapper.h | 1 - sources/shiboken2/libshiboken/signature.cpp | 5 +---- sources/shiboken2/libshiboken/signature.h | 2 +- 11 files changed, 22 insertions(+), 33 deletions(-) diff --git a/sources/pyside2/PySide2/QtQml/pysideqmlregistertype.cpp b/sources/pyside2/PySide2/QtQml/pysideqmlregistertype.cpp index efc86a048..2b60c5c7f 100644 --- a/sources/pyside2/PySide2/QtQml/pysideqmlregistertype.cpp +++ b/sources/pyside2/PySide2/QtQml/pysideqmlregistertype.cpp @@ -484,7 +484,7 @@ void PySide::initQmlSupport(PyObject *module) ElementFactory::init(); // Export QmlListProperty type - if (SbkSpecial_Type_Ready(module, PropertyListTypeF(), PropertyList_SignatureStrings) < 0) { + if (InitSignatureStrings(PropertyListTypeF(), PropertyList_SignatureStrings) < 0) { PyErr_Print(); qWarning() << "Error initializing PropertyList type."; return; @@ -494,7 +494,7 @@ void PySide::initQmlSupport(PyObject *module) PyModule_AddObject(module, PepType_GetNameStr(PropertyListTypeF()), reinterpret_cast(PropertyListTypeF())); - if (SbkSpecial_Type_Ready(module, QtQml_VolatileBoolTypeF(), VolatileBool_SignatureStrings) < 0) { + if (InitSignatureStrings(QtQml_VolatileBoolTypeF(), VolatileBool_SignatureStrings) < 0) { PyErr_Print(); qWarning() << "Error initializing VolatileBool type."; return; diff --git a/sources/pyside2/libpyside/pysideclassinfo.cpp b/sources/pyside2/libpyside/pysideclassinfo.cpp index 375a31b57..aa47f97c1 100644 --- a/sources/pyside2/libpyside/pysideclassinfo.cpp +++ b/sources/pyside2/libpyside/pysideclassinfo.cpp @@ -184,7 +184,7 @@ static const char *ClassInfo_SignatureStrings[] = { void init(PyObject *module) { - if (SbkSpecial_Type_Ready(module, PySideClassInfoTypeF(), ClassInfo_SignatureStrings) < 0) + if (InitSignatureStrings(PySideClassInfoTypeF(), ClassInfo_SignatureStrings) < 0) return; Py_INCREF(PySideClassInfoTypeF()); diff --git a/sources/pyside2/libpyside/pysidemetafunction.cpp b/sources/pyside2/libpyside/pysidemetafunction.cpp index f4b95385a..afb3dbb1a 100644 --- a/sources/pyside2/libpyside/pysidemetafunction.cpp +++ b/sources/pyside2/libpyside/pysidemetafunction.cpp @@ -107,7 +107,7 @@ static const char *MetaFunction_SignatureStrings[] = { void init(PyObject *module) { - if (SbkSpecial_Type_Ready(module, PySideMetaFunctionTypeF(), MetaFunction_SignatureStrings) < 0) + if (InitSignatureStrings(PySideMetaFunctionTypeF(), MetaFunction_SignatureStrings) < 0) return; Py_INCREF(PySideMetaFunctionTypeF()); diff --git a/sources/pyside2/libpyside/pysideproperty.cpp b/sources/pyside2/libpyside/pysideproperty.cpp index 433615e10..8aaa81205 100644 --- a/sources/pyside2/libpyside/pysideproperty.cpp +++ b/sources/pyside2/libpyside/pysideproperty.cpp @@ -366,7 +366,7 @@ static const char *Property_SignatureStrings[] = { void init(PyObject *module) { - if (SbkSpecial_Type_Ready(module, PySidePropertyTypeF(), Property_SignatureStrings) < 0) + if (InitSignatureStrings(PySidePropertyTypeF(), Property_SignatureStrings) < 0) return; Py_INCREF(PySidePropertyTypeF()); diff --git a/sources/pyside2/libpyside/pysidesignal.cpp b/sources/pyside2/libpyside/pysidesignal.cpp index b7941f8d6..5c030316e 100644 --- a/sources/pyside2/libpyside/pysidesignal.cpp +++ b/sources/pyside2/libpyside/pysidesignal.cpp @@ -639,17 +639,17 @@ static const char *SignalInstance_SignatureStrings[] = { void init(PyObject *module) { - if (SbkSpecial_Type_Ready(module, PySideMetaSignalTypeF(), MetaSignal_SignatureStrings) < 0) + if (InitSignatureStrings(PySideMetaSignalTypeF(), MetaSignal_SignatureStrings) < 0) return; Py_INCREF(PySideMetaSignalTypeF()); PyModule_AddObject(module, "MetaSignal", reinterpret_cast(PySideMetaSignalTypeF())); - if (SbkSpecial_Type_Ready(module, PySideSignalTypeF(), Signal_SignatureStrings) < 0) + if (InitSignatureStrings(PySideSignalTypeF(), Signal_SignatureStrings) < 0) return; Py_INCREF(PySideSignalTypeF()); PyModule_AddObject(module, "Signal", reinterpret_cast(PySideSignalTypeF())); - if (SbkSpecial_Type_Ready(module, PySideSignalInstanceTypeF(), SignalInstance_SignatureStrings) < 0) + if (InitSignatureStrings(PySideSignalInstanceTypeF(), SignalInstance_SignatureStrings) < 0) return; Py_INCREF(PySideSignalInstanceTypeF()); PyModule_AddObject(module, "SignalInstance", reinterpret_cast(PySideSignalInstanceTypeF())); diff --git a/sources/pyside2/libpyside/pysideslot.cpp b/sources/pyside2/libpyside/pysideslot.cpp index 7bfd1719a..1ec24ab21 100644 --- a/sources/pyside2/libpyside/pysideslot.cpp +++ b/sources/pyside2/libpyside/pysideslot.cpp @@ -188,7 +188,7 @@ static const char *Slot_SignatureStrings[] = { void init(PyObject *module) { - if (SbkSpecial_Type_Ready(module, PySideSlotTypeF(), Slot_SignatureStrings) < 0) + if (InitSignatureStrings(PySideSlotTypeF(), Slot_SignatureStrings) < 0) return; Py_INCREF(PySideSlotTypeF()); diff --git a/sources/shiboken2/generator/shiboken2/cppgenerator.cpp b/sources/shiboken2/generator/shiboken2/cppgenerator.cpp index 304a7ba4e..2f76b6dbf 100644 --- a/sources/shiboken2/generator/shiboken2/cppgenerator.cpp +++ b/sources/shiboken2/generator/shiboken2/cppgenerator.cpp @@ -595,7 +595,7 @@ void CppGenerator::generateClass(QTextStream &s, const GeneratorContext &classCo sorter.sort(); s << '\n'; - s << "static const char *" << className << "_properties[] = {\n"; + s << "static const char *" << className << "_PropertyStrings[] = {\n"; for (const auto &entry : qAsConst(sorter)) s << INDENT << entry << ",\n"; s << INDENT << NULL_PTR << " // Sentinel\n"; @@ -5171,10 +5171,7 @@ void CppGenerator::writeClassRegister(QTextStream &s, // 4:typeSpec s << INDENT << '&' << chopType(pyTypeName) << "_spec,\n"; - // 5:signatureStrings - s << INDENT << initFunctionName << "_SignatureStrings,\n"; - - // 6:cppObjDtor + // 5:cppObjDtor s << INDENT; if (!metaClass->isNamespace() && !metaClass->hasPrivateDestructor()) { QString dtorClassName = metaClass->qualifiedCppName(); @@ -5190,7 +5187,7 @@ void CppGenerator::writeClassRegister(QTextStream &s, s << "0,\n"; } - // 7:baseType + // 6:baseType const auto base = metaClass->isNamespace() ? metaClass->extendedNamespace() : metaClass->baseClass(); if (base) { @@ -5200,13 +5197,13 @@ void CppGenerator::writeClassRegister(QTextStream &s, s << INDENT << "0,\n"; } - // 8:baseTypes + // 7:baseTypes if (metaClass->baseClassNames().size() > 1) s << INDENT << pyTypeBasesVariable << ',' << Qt::endl; else s << INDENT << "0,\n"; - // 9:wrapperflags + // 8:wrapperflags QByteArrayList wrapperFlags; if (enc) wrapperFlags.append(QByteArrayLiteral("Shiboken::ObjectType::WrapperFlags::InnerClass")); @@ -5220,11 +5217,12 @@ void CppGenerator::writeClassRegister(QTextStream &s, s << INDENT << ");\n"; s << INDENT << Qt::endl; - if (usePySideExtensions()) { - QString className = metaClass->qualifiedCppName(); + s << INDENT << "auto pyType = reinterpret_cast(" << typePtr << ");\n"; + s << INDENT << "InitSignatureStrings(pyType, " << initFunctionName << "_SignatureStrings);\n"; + + if (usePySideExtensions()) s << INDENT << "SbkObjectType_SetPropertyStrings(reinterpret_cast(" << typePtr << "), " - << chopType(pyTypeName) << "_properties);\n"; - } + << chopType(pyTypeName) << "_PropertyStrings);\n"; if (!classContext.forSmartPointer()) s << INDENT << cpythonTypeNameExt(classTypeEntry) << Qt::endl; diff --git a/sources/shiboken2/libshiboken/basewrapper.cpp b/sources/shiboken2/libshiboken/basewrapper.cpp index 6c4dea642..d866d133c 100644 --- a/sources/shiboken2/libshiboken/basewrapper.cpp +++ b/sources/shiboken2/libshiboken/basewrapper.cpp @@ -1102,7 +1102,6 @@ introduceWrapperType(PyObject *enclosingObject, const char *typeName, const char *originalName, PyType_Spec *typeSpec, - const char *signatureStrings[], ObjectDestructor cppObjDtor, SbkObjectType *baseType, PyObject *baseTypes, @@ -1127,12 +1126,8 @@ introduceWrapperType(PyObject *enclosingObject, BindingManager::instance().addClassInheritance(baseType, type); } } - // PYSIDE-510: Here is the single change to support signatures. - if (SbkSpecial_Type_Ready(enclosingObject, reinterpret_cast(type), signatureStrings) < 0) { - std::cerr << "Warning: " << __FUNCTION__ << " returns nullptr for " - << typeName << '/' << originalName << " due to SbkSpecial_Type_Ready() failing\n"; + if (PyType_Ready(reinterpret_cast(type)) < 0) return nullptr; - } initPrivateData(type); auto sotp = PepType_SOTP(type); diff --git a/sources/shiboken2/libshiboken/basewrapper.h b/sources/shiboken2/libshiboken/basewrapper.h index 1190f3187..31083522b 100644 --- a/sources/shiboken2/libshiboken/basewrapper.h +++ b/sources/shiboken2/libshiboken/basewrapper.h @@ -234,7 +234,6 @@ LIBSHIBOKEN_API SbkObjectType *introduceWrapperType(PyObject *enclosingObject, const char *typeName, const char *originalName, PyType_Spec *typeSpec, - const char *signatureStrings[], ObjectDestructor cppObjDtor, SbkObjectType *baseType, PyObject *baseTypes, diff --git a/sources/shiboken2/libshiboken/signature.cpp b/sources/shiboken2/libshiboken/signature.cpp index 242fe18c1..3498829e7 100644 --- a/sources/shiboken2/libshiboken/signature.cpp +++ b/sources/shiboken2/libshiboken/signature.cpp @@ -1182,11 +1182,8 @@ static int _build_func_to_type(PyObject *obtype) return 0; } -int SbkSpecial_Type_Ready(PyObject * /* module */, PyTypeObject *type, - const char *signatures[]) +int InitSignatureStrings(PyTypeObject *type, const char *signatures[]) { - if (PyType_Ready(type) < 0) - return -1; auto *ob_type = reinterpret_cast(type); int ret = PySide_BuildSignatureArgs(ob_type, signatures); if (ret < 0) { diff --git a/sources/shiboken2/libshiboken/signature.h b/sources/shiboken2/libshiboken/signature.h index 2a69df9cf..c5f515a7f 100644 --- a/sources/shiboken2/libshiboken/signature.h +++ b/sources/shiboken2/libshiboken/signature.h @@ -45,7 +45,7 @@ extern "C" { -LIBSHIBOKEN_API int SbkSpecial_Type_Ready(PyObject *, PyTypeObject *, const char *[]); +LIBSHIBOKEN_API int InitSignatureStrings(PyTypeObject *, const char *[]); LIBSHIBOKEN_API void FinishSignatureInitialization(PyObject *, const char *[]); LIBSHIBOKEN_API void SetError_Argument(PyObject *, const char *); LIBSHIBOKEN_API PyObject *Sbk_TypeGet___signature__(PyObject *, PyObject *); -- cgit v1.2.3 From d6d220b5d319b37d5940cc87ea615f8c1fb5e4eb Mon Sep 17 00:00:00 2001 From: Friedemann Kleint Date: Thu, 24 Sep 2020 12:02:18 +0200 Subject: shiboken2: Remove unused code from AbstractMetaField Remove the getter/setter functions and related code. Apparently this was some early, unfinished attempt at properties. Task-number: PYSIDE-1019 Change-Id: Iff196da7b7bfb7b30b724405405decf36201b259 Reviewed-by: Christian Tismer --- .../shiboken2/ApiExtractor/abstractmetalang.cpp | 93 ---------------------- sources/shiboken2/ApiExtractor/abstractmetalang.h | 10 --- 2 files changed, 103 deletions(-) diff --git a/sources/shiboken2/ApiExtractor/abstractmetalang.cpp b/sources/shiboken2/ApiExtractor/abstractmetalang.cpp index 0caaaf40c..2c693c70e 100644 --- a/sources/shiboken2/ApiExtractor/abstractmetalang.cpp +++ b/sources/shiboken2/ApiExtractor/abstractmetalang.cpp @@ -1568,16 +1568,6 @@ void AbstractMetaClass::setFunctions(const AbstractMetaFunctionList &functions) } } -bool AbstractMetaClass::hasFieldAccessors() const -{ - for (const AbstractMetaField *field : m_fields) { - if (field->getter() || field->setter()) - return true; - } - - return false; -} - bool AbstractMetaClass::hasDefaultToStringFunction() const { const AbstractMetaFunctionList &funcs = queryFunctionsByName(QLatin1String("toString")); @@ -1773,12 +1763,6 @@ static bool functions_contains(const AbstractMetaFunctionList &l, const Abstract AbstractMetaField::AbstractMetaField() = default; -AbstractMetaField::~AbstractMetaField() -{ - delete m_setter; - delete m_getter; -} - AbstractMetaField *AbstractMetaField::copy() const { auto *returned = new AbstractMetaField; @@ -1810,55 +1794,6 @@ bool AbstractMetaField::isModifiedRemoved(int types) const return false; } -static QString upCaseFirst(const QString &str) -{ - Q_ASSERT(!str.isEmpty()); - QString s = str; - s[0] = s.at(0).toUpper(); - return s; -} - -static AbstractMetaFunction *createXetter(const AbstractMetaField *g, const QString &name, - AbstractMetaAttributes::Attributes type) -{ - auto *f = new AbstractMetaFunction; - - f->setName(name); - f->setOriginalName(name); - f->setOwnerClass(g->enclosingClass()); - f->setImplementingClass(g->enclosingClass()); - f->setDeclaringClass(g->enclosingClass()); - - AbstractMetaAttributes::Attributes attr = AbstractMetaAttributes::FinalInTargetLang | type; - if (g->isStatic()) - attr |= AbstractMetaAttributes::Static; - if (g->isPublic()) - attr |= AbstractMetaAttributes::Public; - else if (g->isProtected()) - attr |= AbstractMetaAttributes::Protected; - else - attr |= AbstractMetaAttributes::Private; - f->setAttributes(attr); - f->setOriginalAttributes(attr); - - const FieldModificationList &mods = g->modifications(); - for (const FieldModification &mod : mods) { - if (mod.isRenameModifier()) - f->setName(mod.renamedTo()); - if (mod.isAccessModifier()) { - if (mod.isPrivate()) - f->setVisibility(AbstractMetaAttributes::Private); - else if (mod.isProtected()) - f->setVisibility(AbstractMetaAttributes::Protected); - else if (mod.isPublic()) - f->setVisibility(AbstractMetaAttributes::Public); - else if (mod.isFriendly()) - f->setVisibility(AbstractMetaAttributes::Friendly); - } - } - return f; -} - FieldModificationList AbstractMetaField::modifications() const { const FieldModificationList &mods = enclosingClass()->typeEntry()->fieldModifications(); @@ -1872,22 +1807,6 @@ FieldModificationList AbstractMetaField::modifications() const return returned; } -const AbstractMetaFunction *AbstractMetaField::setter() const -{ - if (!m_setter) { - m_setter = createXetter(this, - QLatin1String("set") + upCaseFirst(name()), - AbstractMetaAttributes::SetterFunction); - AbstractMetaArgumentList arguments; - auto *argument = new AbstractMetaArgument; - argument->setType(type()->copy()); - argument->setName(name()); - arguments.append(argument); - m_setter->setArguments(arguments); - } - return m_setter; -} - const AbstractMetaClass *EnclosingClassMixin::targetLangEnclosingClass() const { auto result = m_enclosingClass; @@ -1896,18 +1815,6 @@ const AbstractMetaClass *EnclosingClassMixin::targetLangEnclosingClass() const return result; } -const AbstractMetaFunction *AbstractMetaField::getter() const -{ - if (!m_getter) { - m_getter = createXetter(this, - name(), - AbstractMetaAttributes::GetterFunction); - m_getter->setType(type()); - } - - return m_getter; -} - #ifndef QT_NO_DEBUG_STREAM static void formatMetaAttributes(QDebug &d, AbstractMetaAttributes::Attributes value) { diff --git a/sources/shiboken2/ApiExtractor/abstractmetalang.h b/sources/shiboken2/ApiExtractor/abstractmetalang.h index c17051f45..268546d6c 100644 --- a/sources/shiboken2/ApiExtractor/abstractmetalang.h +++ b/sources/shiboken2/ApiExtractor/abstractmetalang.h @@ -717,10 +717,6 @@ class AbstractMetaField : public AbstractMetaVariable, public AbstractMetaAttrib { public: AbstractMetaField(); - ~AbstractMetaField(); - - const AbstractMetaFunction *getter() const; - const AbstractMetaFunction *setter() const; FieldModificationList modifications() const; @@ -733,10 +729,6 @@ public: static AbstractMetaField * find(const AbstractMetaFieldList &haystack, const QString &needle); - -private: - mutable AbstractMetaFunction *m_getter = nullptr; - mutable AbstractMetaFunction *m_setter = nullptr; }; #ifndef QT_NO_DEBUG_STREAM @@ -1538,8 +1530,6 @@ public: m_templateArgs = args; } - bool hasFieldAccessors() const; - // only valid during metabuilder's run QStringList baseClassNames() const { -- cgit v1.2.3 From 0c8ada75b639ad8689d165ef617f25aeddfc16eb Mon Sep 17 00:00:00 2001 From: Friedemann Kleint Date: Thu, 24 Sep 2020 13:33:28 +0200 Subject: shiboken2: Fix formatting of field getter code Properly indent and join the else if statement. Task-number: PYSIDE-1019 Change-Id: I8d5dc6c84e19b97b55f1ba29094da1e31dc7ca1c Reviewed-by: Christian Tismer --- sources/shiboken2/generator/shiboken2/cppgenerator.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/sources/shiboken2/generator/shiboken2/cppgenerator.cpp b/sources/shiboken2/generator/shiboken2/cppgenerator.cpp index 2f76b6dbf..32b9cf24f 100644 --- a/sources/shiboken2/generator/shiboken2/cppgenerator.cpp +++ b/sources/shiboken2/generator/shiboken2/cppgenerator.cpp @@ -4470,11 +4470,16 @@ void CppGenerator::writeGetterFunction(QTextStream &s, << "reinterpret_cast(self), reinterpret_cast(" << cpythonTypeNameExt(fieldType) << ")));\n"; - s << INDENT << "if (pyOut) {Py_IncRef(pyOut); return pyOut;}\n"; + s << INDENT << "if (pyOut) {\n"; + { + Indentation indent(INDENT); + s << INDENT << "Py_IncRef(pyOut);\n" + << INDENT << "return pyOut;\n"; + } + s << INDENT << "}\n"; } - s << INDENT << "}\n"; // Check if field wrapper has already been created. - s << INDENT << "else if (Shiboken::BindingManager::instance().hasWrapper(" << cppField << ")) {" << "\n"; + s << INDENT << "} else if (Shiboken::BindingManager::instance().hasWrapper(" << cppField << ")) {" << "\n"; { Indentation indent(INDENT); s << INDENT << "pyOut = reinterpret_cast(Shiboken::BindingManager::instance().retrieveWrapper(" -- cgit v1.2.3 From 027e380dc9f658925d5d28d6eb368749afb6be65 Mon Sep 17 00:00:00 2001 From: Christian Tismer Date: Wed, 23 Sep 2020 18:44:23 +0200 Subject: Signature: Break the source into multiple files The signature module has grown groups of rather unrelated topics. In order to reduce the complexity, we break up the source into a number of files. Reason for this change was building support for selectable features, which should not get lost in all the unrelated helper functions. Task-number: PYSIDE-510 Task-number: PYSIDE-1019 Change-Id: I8e22a91db1882f8c5428b8def13bf9f1cea431fb Reviewed-by: Friedemann Kleint --- sources/shiboken2/libshiboken/CMakeLists.txt | 12 +- sources/shiboken2/libshiboken/signature.cpp | 1261 -------------------- sources/shiboken2/libshiboken/signature.h | 2 - .../shiboken2/libshiboken/signature/signature.cpp | 479 ++++++++ .../libshiboken/signature/signature_doc.rst | 357 ++++++ .../libshiboken/signature/signature_extend.cpp | 294 +++++ .../libshiboken/signature/signature_globals.cpp | 295 +++++ .../libshiboken/signature/signature_helper.cpp | 364 ++++++ .../shiboken2/libshiboken/signature/signature_p.h | 106 ++ sources/shiboken2/libshiboken/signature_doc.rst | 357 ------ 10 files changed, 1905 insertions(+), 1622 deletions(-) delete mode 100644 sources/shiboken2/libshiboken/signature.cpp create mode 100644 sources/shiboken2/libshiboken/signature/signature.cpp create mode 100644 sources/shiboken2/libshiboken/signature/signature_doc.rst create mode 100644 sources/shiboken2/libshiboken/signature/signature_extend.cpp create mode 100644 sources/shiboken2/libshiboken/signature/signature_globals.cpp create mode 100644 sources/shiboken2/libshiboken/signature/signature_helper.cpp create mode 100644 sources/shiboken2/libshiboken/signature/signature_p.h delete mode 100644 sources/shiboken2/libshiboken/signature_doc.rst diff --git a/sources/shiboken2/libshiboken/CMakeLists.txt b/sources/shiboken2/libshiboken/CMakeLists.txt index dee5dbd21..a53402e0f 100644 --- a/sources/shiboken2/libshiboken/CMakeLists.txt +++ b/sources/shiboken2/libshiboken/CMakeLists.txt @@ -57,14 +57,19 @@ sbkstaticstrings.cpp bindingmanager.cpp threadstatesaver.cpp shibokenbuffer.cpp -signature.cpp qapp_macro.cpp pep384impl.cpp voidptr.cpp typespec.cpp bufferprocs_py37.cpp + embed/signature_bootstrap_inc.h embed/signature_inc.h + +signature/signature.cpp +signature/signature_globals.cpp +signature/signature_extend.cpp +signature/signature_helper.cpp ) get_numpy_location() @@ -137,12 +142,15 @@ install(FILES shibokenbuffer.h sbkpython.h pep384impl.h - signature.h qapp_macro.h voidptr.h typespec.h bufferprocs_py37.h "${CMAKE_CURRENT_BINARY_DIR}/sbkversion.h" + + signature.h + signature/signature_p.h + DESTINATION include/shiboken2${shiboken2_SUFFIX}) install(TARGETS libshiboken EXPORT Shiboken2Targets LIBRARY DESTINATION "${LIB_INSTALL_DIR}" diff --git a/sources/shiboken2/libshiboken/signature.cpp b/sources/shiboken2/libshiboken/signature.cpp deleted file mode 100644 index 3498829e7..000000000 --- a/sources/shiboken2/libshiboken/signature.cpp +++ /dev/null @@ -1,1261 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2018 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 "basewrapper.h" -#include "autodecref.h" -#include "sbkstring.h" -#include "sbkstaticstrings.h" -#include "sbkstaticstrings_p.h" - -using namespace Shiboken; - -extern "C" -{ - -/* - * The documentation is located in file signature_doc.rst - */ -#include "signature.h" -#include - -// These constants were needed in former versions of the module: -#define PYTHON_HAS_QUALNAME (PY_VERSION_HEX >= 0x03030000) -#define PYTHON_HAS_WEAKREF_PYCFUNCTION (PY_VERSION_HEX >= 0x030500A0) -#define PYTHON_IS_PYTHON3 (PY_VERSION_HEX >= 0x03000000) -#define PYTHON_HAS_KEYWORDONLY (PYTHON_IS_PYTHON3) -#define PYTHON_USES_PERCENT_V_FORMAT (PYTHON_IS_PYTHON3) -#define PYTHON_USES_D_COMMON (PY_VERSION_HEX >= 0x03020000) -#define PYTHON_HAS_DESCR_REDUCE (PY_VERSION_HEX >= 0x03040000) -#define PYTHON_HAS_METH_REDUCE (PYTHON_HAS_DESCR_REDUCE) -#define PYTHON_NEEDS_ITERATOR_FLAG (!PYTHON_IS_PYTHON3) -#define PYTHON_EXPOSES_METHODDESCR (PYTHON_IS_PYTHON3) -#define PYTHON_NO_TYPE_IN_FUNCTIONS (!PYTHON_IS_PYTHON3 || Py_LIMITED_API) -#define PYTHON_HAS_INT_AND_LONG (!PYTHON_IS_PYTHON3) - -// These constants are still in use: -#define PYTHON_USES_UNICODE (PY_VERSION_HEX >= 0x03000000) - -typedef struct safe_globals_struc { - // init part 1: get arg_dict - PyObject *helper_module; - PyObject *arg_dict; - PyObject *map_dict; - PyObject *value_dict; // for writing signatures - PyObject *feature_dict; // registry for PySide.support.__feature__ - // init part 2: run module - PyObject *pyside_type_init_func; - PyObject *create_signature_func; - PyObject *seterror_argument_func; - PyObject *make_helptext_func; - PyObject *finish_import_func; -} safe_globals_struc, *safe_globals; - -static safe_globals pyside_globals = nullptr; - -static PyObject *GetTypeKey(PyObject *ob); - -static PyObject *GetSignature_Function(PyObject *, PyObject *); -static PyObject *GetSignature_TypeMod(PyObject *, PyObject *); -static PyObject *GetSignature_Wrapper(PyObject *, PyObject *); -static PyObject *get_signature(PyObject *self, PyObject *args); -static PyObject *get_signature_intern(PyObject *ob, PyObject *modifier); - -static PyObject *PySide_BuildSignatureProps(PyObject *class_mod); - -static void init_module_1(void); -static void init_module_2(void); -static PyObject *_init_pyside_extension(PyObject * /* self */, PyObject * /* args */); - -static PyObject *CreateSignature(PyObject *props, PyObject *key) -{ - /* - * Here is the new function to create all signatures. It simply calls - * into Python and creates a signature object directly. - * This is so much simpler than using all the attributes explicitly - * to support '_signature_is_functionlike()'. - */ - return PyObject_CallFunction(pyside_globals->create_signature_func, - const_cast("(OO)"), props, key); -} - -typedef PyObject *(*signaturefunc)(PyObject *, PyObject *); - -static PyObject *_get_written_signature(signaturefunc sf, PyObject *ob, PyObject *modifier) -{ - /* - * Be a writable Attribute, but have a computed value. - * - * If a signature has not been written, call the signature function. - * If it has been written, return the written value. - * After __del__ was called, the function value re-appears. - * - * Note: This serves also for the new version that does not allow any - * assignment if we have a computed value. We only need to check if - * a computed value exists and then forbid writing. - * See pyside_set___signature - */ - PyObject *ret = PyDict_GetItem(pyside_globals->value_dict, ob); - if (ret == nullptr) - return ob == nullptr ? nullptr : sf(ob, modifier); - Py_INCREF(ret); - return ret; -} - -static PyObject *pyside_cf_get___signature__(PyObject *func, PyObject *modifier) -{ - init_module_2(); - return _get_written_signature(GetSignature_Function, func, modifier); -} - -static PyObject *pyside_sm_get___signature__(PyObject *sm, PyObject *modifier) -{ - init_module_2(); - AutoDecRef func(PyObject_GetAttr(sm, PyMagicName::func())); - if (Py_TYPE(func) == PepFunction_TypePtr) - return PyObject_GetAttr(func, PyMagicName::signature()); - return _get_written_signature(GetSignature_Function, func, modifier); -} - -static PyObject *_get_class_of_cf(PyObject *ob_cf) -{ - PyObject *selftype = PyCFunction_GET_SELF(ob_cf); - if (selftype == nullptr) { - selftype = PyDict_GetItem(pyside_globals->map_dict, ob_cf); - if (selftype == nullptr) { - // This must be an overloaded function that we handled special. - AutoDecRef special(Py_BuildValue("(OO)", ob_cf, PyName::overload())); - selftype = PyDict_GetItem(pyside_globals->map_dict, special); - if (selftype == nullptr) { - // This is probably a module function. We will return type(None). - selftype = Py_None; - } - } - } - - PyObject *obtype_mod = (PyType_Check(selftype) || PyModule_Check(selftype)) - ? selftype - : reinterpret_cast(Py_TYPE(selftype)); - Py_INCREF(obtype_mod); - return obtype_mod; -} - -static PyObject *_get_class_of_sm(PyObject *ob_sm) -{ - AutoDecRef func(PyObject_GetAttr(ob_sm, PyMagicName::func())); - return _get_class_of_cf(func); -} - -static PyObject *_get_class_of_descr(PyObject *ob) -{ - return PyObject_GetAttr(ob, PyMagicName::objclass()); -} - -static PyObject *GetClassOrModOf(PyObject *ob) -{ - /* - * Return the type or module of a function or type. - * The purpose is finally to use the name of the object. - */ - if (PyType_Check(ob)) { - // PySide-928: The type case must do refcounting like the others as well. - Py_INCREF(ob); - return ob; - } - if (PyType_IsSubtype(Py_TYPE(ob), &PyCFunction_Type)) - return _get_class_of_cf(ob); - if (Py_TYPE(ob) == PepStaticMethod_TypePtr) - return _get_class_of_sm(ob); - if (Py_TYPE(ob) == PepMethodDescr_TypePtr) - return _get_class_of_descr(ob); - if (Py_TYPE(ob) == &PyWrapperDescr_Type) - return _get_class_of_descr(ob); - Py_FatalError("unexpected type in GetClassOrModOf"); - return nullptr; -} - -static PyObject *get_funcname(PyObject *ob) -{ - PyObject *func = ob; - if (Py_TYPE(ob) == PepStaticMethod_TypePtr) - func = PyObject_GetAttr(ob, PyMagicName::func()); - else - Py_INCREF(func); - PyObject *func_name = PyObject_GetAttr(func, PyMagicName::name()); - Py_DECREF(func); - if (func_name == nullptr) - Py_FatalError("unexpected name problem in compute_name_key"); - return func_name; -} - -static PyObject *compute_name_key(PyObject *ob) -{ - if (PyType_Check(ob)) - return GetTypeKey(ob); - AutoDecRef func_name(get_funcname(ob)); - AutoDecRef type_key(GetTypeKey(GetClassOrModOf(ob))); - return Py_BuildValue("(OO)", type_key.object(), func_name.object()); -} - -static int build_name_key_to_func(PyObject *obtype) -{ - auto *type = reinterpret_cast(obtype); - PyMethodDef *meth = type->tp_methods; - - if (meth == nullptr) - return 0; - - AutoDecRef type_key(GetTypeKey(obtype)); - for (; meth->ml_name != nullptr; meth++) { - AutoDecRef func(PyCFunction_NewEx(meth, obtype, nullptr)); - AutoDecRef func_name(get_funcname(func)); - AutoDecRef name_key(Py_BuildValue("(OO)", type_key.object(), func_name.object())); - if (func.isNull() || name_key.isNull() - || PyDict_SetItem(pyside_globals->map_dict, name_key, func) < 0) - return -1; - } - return 0; -} - -static PyObject *name_key_to_func(PyObject *ob) -{ - /* - * We build a mapping from name_key to function. - * This could also be computed directly, but the Limited API - * makes this impossible. So we always build our own mapping. - */ - AutoDecRef name_key(compute_name_key(ob)); - if (name_key.isNull()) - Py_RETURN_NONE; - - PyObject *ret = PyDict_GetItem(pyside_globals->map_dict, name_key); - if (ret == nullptr) { - // do a lazy initialization - AutoDecRef type_key(GetTypeKey(GetClassOrModOf(ob))); - PyObject *type = PyDict_GetItem(pyside_globals->map_dict, - type_key); - if (type == nullptr) - Py_RETURN_NONE; - assert(PyType_Check(type)); - if (build_name_key_to_func(type) < 0) - return nullptr; - ret = PyDict_GetItem(pyside_globals->map_dict, name_key); - } - Py_XINCREF(ret); - return ret; -} - -static PyObject *pyside_md_get___signature__(PyObject *ob_md, PyObject *modifier) -{ - init_module_2(); - AutoDecRef func(name_key_to_func(ob_md)); - if (func.object() == Py_None) - return Py_None; - if (func.isNull()) - Py_FatalError("missing mapping in MethodDescriptor"); - return pyside_cf_get___signature__(func, modifier); -} - -static PyObject *pyside_wd_get___signature__(PyObject *ob, PyObject *modifier) -{ - init_module_2(); - return _get_written_signature(GetSignature_Wrapper, ob, modifier); -} - -static PyObject *pyside_tp_get___signature__(PyObject *obtype_mod, PyObject *modifier) -{ - init_module_2(); - return _get_written_signature(GetSignature_TypeMod, obtype_mod, modifier); -} - -// forward -static PyObject *GetSignature_Cached(PyObject *props, PyObject *func_kind, PyObject *modifier); - -// Helper for __qualname__ which might not always exist in Python 2 (type). -static PyObject *_get_qualname(PyObject *ob) -{ - // We support __qualname__ for types, only. - assert(PyType_Check(ob)); - PyObject *name = PyObject_GetAttr(ob, PyMagicName::qualname()); - if (name == nullptr) { - PyErr_Clear(); - name = PyObject_GetAttr(ob, PyMagicName::name()); - } - return name; -} - -static PyObject *GetTypeKey(PyObject *ob) -{ - assert(PyType_Check(ob) || PyModule_Check(ob)); - /* - * We obtain a unique key using the module name and the type name. - * - * The type name is a bit funny when modules are nested. - * Example: - * - * "sample.Photon.ValueIdentity" is a class. - * name: "ValueIdentity" - * module: "sample.Photon" - * - * This is the PyCFunction behavior, as opposed to Python functions. - */ - // PYSIDE-1286: We use correct __module__ and __qualname__, now. - AutoDecRef module_name(PyObject_GetAttr(ob, PyMagicName::module())); - if (module_name.isNull()) { - // We have no module_name because this is a module ;-) - PyErr_Clear(); - module_name.reset(PyObject_GetAttr(ob, PyMagicName::name())); - return Py_BuildValue("O", module_name.object()); - } - AutoDecRef class_name(_get_qualname(ob)); - if (class_name.isNull()) { - Py_FatalError("Signature: missing class name in GetTypeKey"); - return nullptr; - } - return Py_BuildValue("(OO)", module_name.object(), class_name.object()); -} - -static PyObject *empty_dict = nullptr; - -static PyObject *TypeKey_to_PropsDict(PyObject *type_key, PyObject *obtype) -{ - PyObject *dict = PyDict_GetItem(pyside_globals->arg_dict, type_key); - if (dict == nullptr) { - if (empty_dict == nullptr) - empty_dict = PyDict_New(); - dict = empty_dict; - } - if (!PyDict_Check(dict)) - dict = PySide_BuildSignatureProps(type_key); - return dict; -} - -static PyObject *GetSignature_Function(PyObject *obfunc, PyObject *modifier) -{ - // make sure that we look into PyCFunction, only... - if (Py_TYPE(obfunc) == PepFunction_TypePtr) - Py_RETURN_NONE; - AutoDecRef obtype_mod(GetClassOrModOf(obfunc)); - AutoDecRef type_key(GetTypeKey(obtype_mod)); - if (type_key.isNull()) - Py_RETURN_NONE; - PyObject *dict = TypeKey_to_PropsDict(type_key, obtype_mod); - if (dict == nullptr) - return nullptr; - AutoDecRef func_name(PyObject_GetAttr(obfunc, PyMagicName::name())); - PyObject *props = !func_name.isNull() ? PyDict_GetItem(dict, func_name) : nullptr; - if (props == nullptr) - Py_RETURN_NONE; - - int flags = PyCFunction_GET_FLAGS(obfunc); - PyObject *func_kind; - if (PyModule_Check(obtype_mod)) - func_kind = PyName::function(); - else if (flags & METH_CLASS) - func_kind = PyName::classmethod(); - else if (flags & METH_STATIC) - func_kind = PyName::staticmethod(); - else - func_kind = PyName::method(); - return GetSignature_Cached(props, func_kind, modifier); -} - -static PyObject *GetSignature_Wrapper(PyObject *ob, PyObject *modifier) -{ - AutoDecRef func_name(PyObject_GetAttr(ob, PyMagicName::name())); - AutoDecRef objclass(PyObject_GetAttr(ob, PyMagicName::objclass())); - AutoDecRef class_key(GetTypeKey(objclass)); - if (func_name.isNull() || objclass.isNull() || class_key.isNull()) - return nullptr; - PyObject *dict = TypeKey_to_PropsDict(class_key, objclass); - if (dict == nullptr) - return nullptr; - PyObject *props = PyDict_GetItem(dict, func_name); - if (props == nullptr) - Py_RETURN_NONE; - return GetSignature_Cached(props, PyName::method(), modifier); -} - -static PyObject *GetSignature_TypeMod(PyObject *ob, PyObject *modifier) -{ - AutoDecRef ob_name(PyObject_GetAttr(ob, PyMagicName::name())); - AutoDecRef ob_key(GetTypeKey(ob)); - - PyObject *dict = TypeKey_to_PropsDict(ob_key, ob); - if (dict == nullptr) - return nullptr; - PyObject *props = PyDict_GetItem(dict, ob_name); - if (props == nullptr) - Py_RETURN_NONE; - return GetSignature_Cached(props, PyName::method(), modifier); -} - -static PyObject *GetSignature_Cached(PyObject *props, PyObject *func_kind, PyObject *modifier) -{ - // Special case: We want to know the func_kind. - if (modifier) { -#if PYTHON_USES_UNICODE - PyUnicode_InternInPlace(&modifier); -#else - PyString_InternInPlace(&modifier); -#endif - if (modifier == PyMagicName::func_kind()) - return Py_BuildValue("O", func_kind); - } - - AutoDecRef key(modifier == nullptr ? Py_BuildValue("O", func_kind) - : Py_BuildValue("(OO)", func_kind, modifier)); - PyObject *value = PyDict_GetItem(props, key); - if (value == nullptr) { - // we need to compute a signature object - value = CreateSignature(props, key); - if (value != nullptr) { - if (PyDict_SetItem(props, key, value) < 0) - // this is an error - return nullptr; - } - else { - // key not found - Py_RETURN_NONE; - } - } - return Py_INCREF(value), value; -} - -static const char *PySide_CompressedSignaturePackage[] = { -#include "embed/signature_inc.h" - }; - -static const unsigned char PySide_SignatureLoader[] = { -#include "embed/signature_bootstrap_inc.h" - }; - -// This function will be inserted into __builtins__. -static PyMethodDef init_methods[] = { - {"_init_pyside_extension", (PyCFunction)_init_pyside_extension, METH_NOARGS}, - {nullptr, nullptr} -}; - -static safe_globals_struc *init_phase_1(PyMethodDef *init_meth) -{ - { - auto *p = reinterpret_cast - (malloc(sizeof(safe_globals_struc))); - if (p == nullptr) - goto error; - /* - * Initializing module signature_bootstrap. - * Since we now have an embedding script, we can do this without any - * Python strings in the C code. - */ -#ifdef Py_LIMITED_API - // We must work for multiple versions, so use source code. -#else - AutoDecRef marshal_module(PyImport_Import(PyName::marshal())); - if (marshal_module.isNull()) - goto error; - AutoDecRef loads(PyObject_GetAttr(marshal_module, PyName::loads())); - if (loads.isNull()) - goto error; -#endif - char *bytes_cast = reinterpret_cast( - const_cast(PySide_SignatureLoader)); - AutoDecRef bytes(PyBytes_FromStringAndSize(bytes_cast, sizeof(PySide_SignatureLoader))); - if (bytes.isNull()) - goto error; -#ifdef Py_LIMITED_API - PyObject *builtins = PyEval_GetBuiltins(); - PyObject *compile = PyDict_GetItem(builtins, PyName::compile()); - if (compile == nullptr) - goto error; - AutoDecRef code_obj(PyObject_CallFunction(compile, "Oss", - bytes.object(), "(builtin)", "exec")); -#else - AutoDecRef code_obj(PyObject_CallFunctionObjArgs( - loads, bytes.object(), nullptr)); -#endif - if (code_obj.isNull()) - goto error; - p->helper_module = PyImport_ExecCodeModule(const_cast - ("signature_bootstrap"), code_obj); - if (p->helper_module == nullptr) - goto error; - // Initialize the module - PyObject *mdict = PyModule_GetDict(p->helper_module); - if (PyDict_SetItem(mdict, PyMagicName::builtins(), PyEval_GetBuiltins()) < 0) - goto error; - /* - * Unpack an embedded ZIP file with more signature modules. - * They will be loaded later with the zipimporter. - * Due to MSVC's limitation to 64k strings, we need to assemble pieces. - */ - const char **block_ptr = (const char **)PySide_CompressedSignaturePackage; - int npieces = 0; - PyObject *piece, *zipped_string_sequence = PyList_New(0); - if (zipped_string_sequence == nullptr) - return nullptr; - for (; **block_ptr != 0; ++block_ptr) { - npieces++; - // we avoid the string/unicode dilemma by not using PyString_XXX: - piece = Py_BuildValue("s", *block_ptr); - if (piece == nullptr || PyList_Append(zipped_string_sequence, piece) < 0) - goto error; - } - if (PyDict_SetItemString(mdict, "zipstring_sequence", zipped_string_sequence) < 0) - goto error; - Py_DECREF(zipped_string_sequence); - - // build a dict for diverse mappings - p->map_dict = PyDict_New(); - if (p->map_dict == nullptr) - goto error; - - // build a dict for the prepared arguments - p->arg_dict = PyDict_New(); - if (p->arg_dict == nullptr - || PyObject_SetAttrString(p->helper_module, "pyside_arg_dict", p->arg_dict) < 0) - goto error; - - // build a dict for assigned signature values - p->value_dict = PyDict_New(); - if (p->value_dict == nullptr) - goto error; - - // PYSIDE-1019: build a __feature__ dict - p->feature_dict = PyDict_New(); - if (p->feature_dict == nullptr - || PyObject_SetAttrString(p->helper_module, "pyside_feature_dict", p->feature_dict) < 0) - goto error; - - // This function will be disabled until phase 2 is done. - p->finish_import_func = nullptr; - - // Initialize the explicit init function. - AutoDecRef init(PyCFunction_NewEx(init_meth, nullptr, nullptr)); - if (init.isNull() - || PyDict_SetItemString(PyEval_GetBuiltins(), init_meth->ml_name, init) != 0) - goto error; - - return p; - } -error: - PyErr_Print(); - Py_FatalError("could not initialize part 1"); - return nullptr; -} - -static int init_phase_2(safe_globals_struc *p, PyMethodDef *methods) -{ - { - PyMethodDef *ml; - - // The single function to be called, but maybe more to come. - for (ml = methods; ml->ml_name != nullptr; ml++) { - PyObject *v = PyCFunction_NewEx(ml, nullptr, nullptr); - if (v == nullptr - || PyObject_SetAttrString(p->helper_module, ml->ml_name, v) != 0) - goto error; - Py_DECREF(v); - } - PyObject *bootstrap_func = PyObject_GetAttrString(p->helper_module, "bootstrap"); - if (bootstrap_func == nullptr) - goto error; - // The return value of the bootstrap function is the loader module. - PyObject *loader = PyObject_CallFunction(bootstrap_func, const_cast("()")); - if (loader == nullptr) - goto error; - // now the loader should be initialized - p->pyside_type_init_func = PyObject_GetAttrString(loader, "pyside_type_init"); - if (p->pyside_type_init_func == nullptr) - goto error; - p->create_signature_func = PyObject_GetAttrString(loader, "create_signature"); - if (p->create_signature_func == nullptr) - goto error; - p->seterror_argument_func = PyObject_GetAttrString(loader, "seterror_argument"); - if (p->seterror_argument_func == nullptr) - goto error; - p->make_helptext_func = PyObject_GetAttrString(loader, "make_helptext"); - if (p->make_helptext_func == nullptr) - goto error; - p->finish_import_func = PyObject_GetAttrString(loader, "finish_import"); - if (p->finish_import_func == nullptr) - goto error; - return 0; - } -error: - PyErr_Print(); - Py_FatalError("could not initialize part 2"); - return -1; -} - -static int _fixup_getset(PyTypeObject *type, const char *name, PyGetSetDef *new_gsp) -{ - /* - * This function pre-fills all fields of the new gsp. We then - * insert the changed values. - */ - PyGetSetDef *gsp = type->tp_getset; - if (gsp != nullptr) { - for (; gsp->name != nullptr; gsp++) { - if (strcmp(gsp->name, name) == 0) { - new_gsp->set = gsp->set; - new_gsp->doc = gsp->doc; - new_gsp->closure = gsp->closure; - return 1; // success - } - } - } - PyMemberDef *md = type->tp_members; - if (md != nullptr) - for (; md->name != nullptr; md++) - if (strcmp(md->name, name) == 0) - return 1; - // staticmethod has just a `__doc__` in the class - assert(strcmp(type->tp_name, "staticmethod") == 0 && strcmp(name, "__doc__") == 0); - return 0; -} - -static int add_more_getsets(PyTypeObject *type, PyGetSetDef *gsp, PyObject **doc_descr) -{ - /* - * This function is used to assign a new `__signature__` attribute, - * and also to override a `__doc__` or `__name__` attribute. - */ - assert(PyType_Check(type)); - PyType_Ready(type); - PyObject *dict = type->tp_dict; - for (; gsp->name != nullptr; gsp++) { - PyObject *have_descr = PyDict_GetItemString(dict, gsp->name); - if (have_descr != nullptr) { - Py_INCREF(have_descr); - if (strcmp(gsp->name, "__doc__") == 0) - *doc_descr = have_descr; - else - assert(false); - if (!_fixup_getset(type, gsp->name, gsp)) - continue; - } - AutoDecRef descr(PyDescr_NewGetSet(type, gsp)); - if (descr.isNull()) - return -1; - if (PyDict_SetItemString(dict, gsp->name, descr) < 0) - return -1; - } - PyType_Modified(type); - return 0; -} - -//////////////////////////////////////////////////////////////////////////// -// -// Augmenting builtin types with a __signature__ attribute. -// -// This is a harmless change to Python, similar like __text_signature__. -// We could avoid it, but then we would need to copy quite some module -// initialization functions which are pretty version- and word size -// dependent. I think this little patch is the lesser of the two evils. -// -// Please note that in fact we are modifying 'type', the metaclass of all -// objects, because we add new functionality. -// -// Addendum 2019-01-12: We now also compute a docstring from the signature. -// - -// keep the original __doc__ functions -static PyObject *old_cf_doc_descr = nullptr; -static PyObject *old_sm_doc_descr = nullptr; -static PyObject *old_md_doc_descr = nullptr; -static PyObject *old_tp_doc_descr = nullptr; -static PyObject *old_wd_doc_descr = nullptr; - -static int handle_doc_in_progress = 0; - -static PyObject *handle_doc(PyObject *ob, PyObject *old_descr) -{ - init_module_1(); - init_module_2(); - AutoDecRef ob_type_mod(GetClassOrModOf(ob)); - const char *name; - if (PyModule_Check(ob_type_mod)) - name = PyModule_GetName(ob_type_mod); - else - name = reinterpret_cast(ob_type_mod.object())->tp_name; - if (handle_doc_in_progress || name == nullptr - || strncmp(name, "PySide2.", 8) != 0) - return PyObject_CallMethodObjArgs(old_descr, - PyMagicName::get(), - ob, nullptr); - handle_doc_in_progress++; - PyObject *res = PyObject_CallFunction( - pyside_globals->make_helptext_func, - const_cast("(O)"), ob); - handle_doc_in_progress--; - if (res == nullptr) { - PyErr_Print(); - Py_FatalError("handle_doc did not receive a result"); - } - return res; -} - -static PyObject *pyside_cf_get___doc__(PyObject *cf) { - return handle_doc(cf, old_cf_doc_descr); -} - -static PyObject *pyside_sm_get___doc__(PyObject *sm) { - return handle_doc(sm, old_sm_doc_descr); -} - -static PyObject *pyside_md_get___doc__(PyObject *md) { - return handle_doc(md, old_md_doc_descr); -} - -static PyObject *pyside_tp_get___doc__(PyObject *tp) { - return handle_doc(tp, old_tp_doc_descr); -} - -static PyObject *pyside_wd_get___doc__(PyObject *wd) { - return handle_doc(wd, old_wd_doc_descr); -} - -// the default setter for all objects -static int pyside_set___signature__(PyObject *op, PyObject *value) -{ - // By this additional check, this function refuses write access. - // We consider both nullptr and Py_None as not been written. - AutoDecRef has_val(get_signature_intern(op, nullptr)); - if (!(has_val.isNull() || has_val == Py_None)) { - PyErr_Format(PyExc_AttributeError, - "Attribute '__signature__' of '%.50s' object is not writable", - Py_TYPE(op)->tp_name); - return -1; - } - int ret = value == nullptr ? PyDict_DelItem(pyside_globals->value_dict, op) - : PyDict_SetItem(pyside_globals->value_dict, op, value); - Py_XINCREF(value); - return ret; -} - -static PyGetSetDef new_PyCFunction_getsets[] = { - {const_cast("__doc__"), (getter)pyside_cf_get___doc__}, - {const_cast("__signature__"), (getter)pyside_cf_get___signature__, - (setter)pyside_set___signature__}, - {nullptr} -}; - -static PyGetSetDef new_PyStaticMethod_getsets[] = { - {const_cast("__doc__"), (getter)pyside_sm_get___doc__}, - {const_cast("__signature__"), (getter)pyside_sm_get___signature__, - (setter)pyside_set___signature__}, - {nullptr} -}; - -static PyGetSetDef new_PyMethodDescr_getsets[] = { - {const_cast("__doc__"), (getter)pyside_md_get___doc__}, - {const_cast("__signature__"), (getter)pyside_md_get___signature__, - (setter)pyside_set___signature__}, - {nullptr} -}; - -static PyGetSetDef new_PyType_getsets[] = { - {const_cast("__doc__"), (getter)pyside_tp_get___doc__}, - {const_cast("__signature__"), (getter)pyside_tp_get___signature__, - (setter)pyside_set___signature__}, - {nullptr} -}; - -static PyGetSetDef new_PyWrapperDescr_getsets[] = { - {const_cast("__doc__"), (getter)pyside_wd_get___doc__}, - {const_cast("__signature__"), (getter)pyside_wd_get___signature__, - (setter)pyside_set___signature__}, - {nullptr} -}; - -//////////////////////////////////////////////////////////////////////////// -// -// get_signature -- providing a superior interface -// -// Additionally to the interface via __signature__, we also provide -// a general function, which allows for different signature layouts. -// The "modifier" argument is a string that is passed in from 'loader.py'. -// Configuration what the modifiers mean is completely in Python. -// - -static PyObject *get_signature_intern(PyObject *ob, PyObject *modifier) -{ - if (PyType_IsSubtype(Py_TYPE(ob), &PyCFunction_Type)) - return pyside_cf_get___signature__(ob, modifier); - if (Py_TYPE(ob) == PepStaticMethod_TypePtr) - return pyside_sm_get___signature__(ob, modifier); - if (Py_TYPE(ob) == PepMethodDescr_TypePtr) - return pyside_md_get___signature__(ob, modifier); - if (PyType_Check(ob)) - return pyside_tp_get___signature__(ob, modifier); - if (Py_TYPE(ob) == &PyWrapperDescr_Type) - return pyside_wd_get___signature__(ob, modifier); - return nullptr; -} - -static PyObject *get_signature(PyObject * /* self */, PyObject *args) -{ - PyObject *ob; - PyObject *modifier = nullptr; - - init_module_1(); - - if (!PyArg_ParseTuple(args, "O|O", &ob, &modifier)) - return nullptr; - if (Py_TYPE(ob) == PepFunction_TypePtr) - Py_RETURN_NONE; - PyObject *ret = get_signature_intern(ob, modifier); - if (ret != nullptr) - return ret; - Py_RETURN_NONE; -} - -static PyObject *_init_pyside_extension(PyObject * /* self */, PyObject * /* args */) -{ - init_module_1(); - init_module_2(); - Py_RETURN_NONE; -} - -//////////////////////////////////////////////////////////////////////////// -// -// This special Type_Ready does certain initializations earlier with -// our new version. -// - -#ifndef _WIN32 -//////////////////////////////////////////////////////////////////////////// -// a stack trace for linux-like platforms -#include -#if defined(__GLIBC__) -# include -#endif -#include -#include -#include - -void handler(int sig) { -#if defined(__GLIBC__) - void *array[30]; - size_t size; - - // get void *'s for all entries on the stack - size = backtrace(array, 30); - - // print out all the frames to stderr -#endif - fprintf(stderr, "Error: signal %d:\n", sig); -#if defined(__GLIBC__) - backtrace_symbols_fd(array, size, STDERR_FILENO); -#endif - exit(1); -} - -//////////////////////////////////////////////////////////////////////////// -#endif // _WIN32 - -static int PySide_PatchTypes(void) -{ - static int init_done = 0; - - if (!init_done) { - AutoDecRef meth_descr(PyObject_GetAttrString( - reinterpret_cast(&PyString_Type), "split")); - AutoDecRef wrap_descr(PyObject_GetAttrString( - reinterpret_cast(Py_TYPE(Py_True)), "__add__")); - // abbreviations for readability - auto md_gs = new_PyMethodDescr_getsets; - auto md_doc = &old_md_doc_descr; - auto cf_gs = new_PyCFunction_getsets; - auto cf_doc = &old_cf_doc_descr; - auto sm_gs = new_PyStaticMethod_getsets; - auto sm_doc = &old_sm_doc_descr; - auto tp_gs = new_PyType_getsets; - auto tp_doc = &old_tp_doc_descr; - auto wd_gs = new_PyWrapperDescr_getsets; - auto wd_doc = &old_wd_doc_descr; - - if (meth_descr.isNull() || wrap_descr.isNull() - || PyType_Ready(Py_TYPE(meth_descr)) < 0 - || add_more_getsets(PepMethodDescr_TypePtr, md_gs, md_doc) < 0 - || add_more_getsets(&PyCFunction_Type, cf_gs, cf_doc) < 0 - || add_more_getsets(PepStaticMethod_TypePtr, sm_gs, sm_doc) < 0 - || add_more_getsets(&PyType_Type, tp_gs, tp_doc) < 0 - || add_more_getsets(Py_TYPE(wrap_descr), wd_gs, wd_doc) < 0 - ) - return -1; -#ifndef _WIN32 - // We enable the stack trace in CI, only. - const char *testEnv = getenv("QTEST_ENVIRONMENT"); - if (testEnv && strstr(testEnv, "ci")) - signal(SIGSEGV, handler); // install our handler -#endif // _WIN32 - init_done = 1; - } - return 0; -} - -static void init_module_1(void) -{ - static int init_done = 0; - - if (!init_done) { - pyside_globals = init_phase_1(init_methods); - if (pyside_globals != nullptr) - init_done = 1; - } -} - -static int PySide_BuildSignatureArgs(PyObject *obtype_mod, const char *signatures[]) -{ - init_module_1(); - AutoDecRef type_key(GetTypeKey(obtype_mod)); - /* - * PYSIDE-996: Avoid string overflow in MSVC, which has a limit of - * 2**15 unicode characters (64 K memory). - * Instead of one huge string, we take a ssize_t that is the - * address of a string array. It will not be turned into a real - * string list until really used by Python. This is quite optimal. - */ - AutoDecRef numkey(Py_BuildValue("n", signatures)); - if (type_key.isNull() || numkey.isNull() - || PyDict_SetItem(pyside_globals->arg_dict, type_key, numkey) < 0) - return -1; - /* - * We record also a mapping from type key to type/module. This helps to - * lazily initialize the Py_LIMITED_API in name_key_to_func(). - */ - return PyDict_SetItem(pyside_globals->map_dict, type_key, obtype_mod) == 0 ? 0 : -1; -} - -static PyMethodDef signature_methods[] = { - {"get_signature", (PyCFunction)get_signature, METH_VARARGS, - "get the __signature__, but pass an optional string parameter"}, - {nullptr, nullptr} -}; - -static void init_module_2(void) -{ - static int init_done = 0; - - if (!init_done) { - // Phase 2 will call __init__.py which touches a signature, itself. - // Therefore we set init_done prior to init_phase_2(). - init_done = 1; - init_phase_2(pyside_globals, signature_methods); - } -} - -static PyObject *_address_to_stringlist(PyObject *numkey) -{ - ssize_t address = PyNumber_AsSsize_t(numkey, PyExc_ValueError); - if (address == -1 && PyErr_Occurred()) - return nullptr; - char **sig_strings = reinterpret_cast(address); - PyObject *res_list = PyList_New(0); - if (res_list == nullptr) - return nullptr; - for (; *sig_strings != nullptr; ++sig_strings) { - char *sig_str = *sig_strings; - AutoDecRef pystr(Py_BuildValue("s", sig_str)); - if (pystr.isNull() || PyList_Append(res_list, pystr) < 0) - return nullptr; - } - return res_list; -} - -static PyObject *PySide_BuildSignatureProps(PyObject *type_key) -{ - /* - * Here is the second part of the function. - * This part will be called on-demand when needed by some attribute. - * We simply pick up the arguments that we stored here and replace - * them by the function result. - */ - init_module_2(); - if (type_key == nullptr) - return nullptr; - PyObject *numkey = PyDict_GetItem(pyside_globals->arg_dict, type_key); - AutoDecRef strings(_address_to_stringlist(numkey)); - if (strings.isNull()) - return nullptr; - AutoDecRef arg_tup(Py_BuildValue("(OO)", type_key, strings.object())); - if (arg_tup.isNull()) - return nullptr; - PyObject *dict = PyObject_CallObject(pyside_globals->pyside_type_init_func, arg_tup); - if (dict == nullptr) { - if (PyErr_Occurred()) - return nullptr; - // No error: return an empty dict. - if (empty_dict == nullptr) - empty_dict = PyDict_New(); - return empty_dict; - } - // We replace the arguments by the result dict. - if (PyDict_SetItem(pyside_globals->arg_dict, type_key, dict) < 0) - return nullptr; - return dict; -} - -static int _finish_nested_classes(PyObject *dict); -static int _build_func_to_type(PyObject *obtype); - -static int PySide_FinishSignatures(PyObject *module, const char *signatures[]) -{ - /* - * Initialization of module functions and resolving of static methods. - */ - const char *name = PyModule_GetName(module); - if (name == nullptr) - return -1; - - // we abuse the call for types, since they both have a __name__ attribute. - if (PySide_BuildSignatureArgs(module, signatures) < 0) - return -1; - - /* - * Note: This function crashed when called from PySide_BuildSignatureArgs. - * Probably this was too early. - * - * Pep384: We need to switch this always on since we have no access - * to the PyCFunction attributes. Therefore I simplified things - * and always use our own mapping. - */ - PyObject *key, *func, *obdict = PyModule_GetDict(module); - Py_ssize_t pos = 0; - - while (PyDict_Next(obdict, &pos, &key, &func)) - if (PyCFunction_Check(func)) - if (PyDict_SetItem(pyside_globals->map_dict, func, module) < 0) - return -1; - if (_finish_nested_classes(obdict) < 0) - return -1; - // The finish_import function will not work the first time since phase 2 - // was not yet run. But that is ok, because the first import is always for - // the shiboken module (or a test module). - if (pyside_globals->finish_import_func == nullptr) { - assert(strncmp(name, "PySide2.", 8) != 0); - return 0; - } - AutoDecRef ret(PyObject_CallFunction( - pyside_globals->finish_import_func, const_cast("(O)"), module)); - return ret.isNull() ? -1 : 0; -} - -static int _finish_nested_classes(PyObject *obdict) -{ - PyObject *key, *value, *obtype; - PyTypeObject *subtype; - Py_ssize_t pos = 0; - - if (obdict == nullptr) - return -1; - while (PyDict_Next(obdict, &pos, &key, &value)) { - if (PyType_Check(value)) { - obtype = value; - if (_build_func_to_type(obtype) < 0) - return -1; - // now continue with nested cases - subtype = reinterpret_cast(obtype); - if (_finish_nested_classes(subtype->tp_dict) < 0) - return -1; - } - } - return 0; -} - -static int _build_func_to_type(PyObject *obtype) -{ - /* - * There is no general way to directly get the type of a static method. - * On Python 3, the type is hidden in an unused pointer in the - * PyCFunction structure, but the Limited API does not allow to access - * this, either. - * - * In the end, it was easier to avoid such tricks and build an explicit - * mapping from function to type. - * - * We walk through the method list of the type - * and record the mapping from static method to this type in a dict. - * We also check for hidden methods, see below. - */ - auto *type = reinterpret_cast(obtype); - PyObject *dict = type->tp_dict; - PyMethodDef *meth = type->tp_methods; - - if (meth == nullptr) - return 0; - - for (; meth->ml_name != nullptr; meth++) { - /* - * It is possible that a method is overwritten by another - * attribute with the same name. This case was obviously provoked - * explicitly in "testbinding.TestObject.staticMethodDouble", - * where instead of the method a "PySide2.QtCore.Signal" object - * was in the dict. - * This overlap is also found in regular PySide under - * "PySide2.QtCore.QProcess.error" where again a signal object is - * returned. These hidden methods will be opened for the - * signature module by adding them under the name - * "{name}.overload". - */ - PyObject *descr = PyDict_GetItemString(dict, meth->ml_name); - PyObject *look_attr = meth->ml_flags & METH_STATIC ? PyMagicName::func() - : PyMagicName::name(); - int check_name = meth->ml_flags & METH_STATIC ? 0 : 1; - if (descr == nullptr) - return -1; - - // We first check all methods if one is hidden by something else. - AutoDecRef look(PyObject_GetAttr(descr, look_attr)); - AutoDecRef given(Py_BuildValue("s", meth->ml_name)); - if (look.isNull() - || (check_name && PyObject_RichCompareBool(look, given, Py_EQ) != 1)) { - PyErr_Clear(); - AutoDecRef cfunc(PyCFunction_NewEx( - meth, reinterpret_cast(type), nullptr)); - if (cfunc.isNull()) - return -1; - if (meth->ml_flags & METH_STATIC) - descr = PyStaticMethod_New(cfunc); - else - descr = PyDescr_NewMethod(type, meth); - if (descr == nullptr) - return -1; - char mangled_name[200]; - strcpy(mangled_name, meth->ml_name); - strcat(mangled_name, ".overload"); - if (PyDict_SetItemString(dict, mangled_name, descr) < 0) - return -1; - if (meth->ml_flags & METH_STATIC) { - // This is the special case where a static method is hidden. - AutoDecRef special(Py_BuildValue("(Os)", cfunc.object(), "overload")); - if (PyDict_SetItem(pyside_globals->map_dict, special, obtype) < 0) - return -1; - } - if (PyDict_SetItemString(pyside_globals->map_dict, mangled_name, obtype) < 0) - return -1; - continue; - } - // Then we insert the mapping for static methods. - if (meth->ml_flags & METH_STATIC) { - if (PyDict_SetItem(pyside_globals->map_dict, look, obtype) < 0) - return -1; - } - } - return 0; -} - -int InitSignatureStrings(PyTypeObject *type, const char *signatures[]) -{ - auto *ob_type = reinterpret_cast(type); - int ret = PySide_BuildSignatureArgs(ob_type, signatures); - if (ret < 0) { - PyErr_Print(); - PyErr_SetNone(PyExc_ImportError); - } - return ret; -} - -void FinishSignatureInitialization(PyObject *module, const char *signatures[]) -{ - /* - * This function is called at the very end of a module initialization. - * We now patch certain types to support the __signature__ attribute, - * initialize module functions and resolve static methods. - * - * Still, it is not possible to call init phase 2 from here, - * because the import is still running. Do it from Python! - */ - if ( PySide_PatchTypes() < 0 - || PySide_FinishSignatures(module, signatures) < 0) { - PyErr_Print(); - PyErr_SetNone(PyExc_ImportError); - } -} - -void SetError_Argument(PyObject *args, const char *func_name) -{ - /* - * This function replaces the type error construction with extra - * overloads parameter in favor of using the signature module. - * Error messages are rare, so we do it completely in Python. - */ - init_module_1(); - init_module_2(); - AutoDecRef res(PyObject_CallFunction(pyside_globals->seterror_argument_func, - const_cast("(Os)"), args, func_name)); - if (res.isNull()) { - PyErr_Print(); - Py_FatalError("seterror_argument did not receive a result"); - } - PyObject *err, *msg; - if (!PyArg_UnpackTuple(res, func_name, 2, 2, &err, &msg)) { - PyErr_Print(); - Py_FatalError("unexpected failure in seterror_argument"); - } - PyErr_SetObject(err, msg); -} - -/* - * Support for the metatype SbkObjectType_Type's tp_getset. - * - * This was not necessary for __signature__, because PyType_Type inherited it. - * But the __doc__ attribute existed already by inheritance, and calling - * PyType_Modified() is not supported. So we added the getsets explicitly - * to the metatype. - */ - -PyObject *Sbk_TypeGet___signature__(PyObject *ob, PyObject *modifier) -{ - return pyside_tp_get___signature__(ob, modifier); -} - -PyObject *Sbk_TypeGet___doc__(PyObject *ob) -{ - return pyside_tp_get___doc__(ob); -} - -PyObject *GetFeatureDict() -{ - init_module_1(); - return pyside_globals->feature_dict; -} - -} //extern "C" diff --git a/sources/shiboken2/libshiboken/signature.h b/sources/shiboken2/libshiboken/signature.h index c5f515a7f..b77cc0f4c 100644 --- a/sources/shiboken2/libshiboken/signature.h +++ b/sources/shiboken2/libshiboken/signature.h @@ -40,8 +40,6 @@ #ifndef SIGNATURE_H #define SIGNATURE_H -#include "sbkpython.h" - extern "C" { diff --git a/sources/shiboken2/libshiboken/signature/signature.cpp b/sources/shiboken2/libshiboken/signature/signature.cpp new file mode 100644 index 000000000..76a71b00b --- /dev/null +++ b/sources/shiboken2/libshiboken/signature/signature.cpp @@ -0,0 +1,479 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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$ +** +****************************************************************************/ + +//////////////////////////////////////////////////////////////////////////// +// +// signature.cpp +// ------------- +// +// This is the main file of the signature module. +// It contains the most important functions and avoids confusion +// by moving many helper functions elsewhere. +// +// General documentation can be found in `signature_doc.rst`. +// + +#include "basewrapper.h" +#include "autodecref.h" +#include "sbkstring.h" +#include "sbkstaticstrings.h" +#include "sbkstaticstrings_p.h" + +#include "signature_p.h" +#include + +using namespace Shiboken; + +extern "C" +{ + +static PyObject *CreateSignature(PyObject *props, PyObject *key) +{ + /* + * Here is the new function to create all signatures. It simply calls + * into Python and creates a signature object directly. + * This is so much simpler than using all the attributes explicitly + * to support '_signature_is_functionlike()'. + */ + return PyObject_CallFunction(pyside_globals->create_signature_func, + const_cast("(OO)"), props, key); +} + +PyObject *GetClassOrModOf(PyObject *ob) +{ + /* + * Return the type or module of a function or type. + * The purpose is finally to use the name of the object. + */ + if (PyType_Check(ob)) { + // PySide-928: The type case must do refcounting like the others as well. + Py_INCREF(ob); + return ob; + } + if (PyType_IsSubtype(Py_TYPE(ob), &PyCFunction_Type)) + return _get_class_of_cf(ob); + if (Py_TYPE(ob) == PepStaticMethod_TypePtr) + return _get_class_of_sm(ob); + if (Py_TYPE(ob) == PepMethodDescr_TypePtr) + return _get_class_of_descr(ob); + if (Py_TYPE(ob) == &PyWrapperDescr_Type) + return _get_class_of_descr(ob); + Py_FatalError("unexpected type in GetClassOrModOf"); + return nullptr; +} + +PyObject *GetTypeKey(PyObject *ob) +{ + assert(PyType_Check(ob) || PyModule_Check(ob)); + /* + * Obtain a unique key using the module name and the type name. + * + * PYSIDE-1286: We use correct __module__ and __qualname__, now. + */ + AutoDecRef module_name(PyObject_GetAttr(ob, PyMagicName::module())); + if (module_name.isNull()) { + // We have no module_name because this is a module ;-) + PyErr_Clear(); + module_name.reset(PyObject_GetAttr(ob, PyMagicName::name())); + return Py_BuildValue("O", module_name.object()); + } + AutoDecRef class_name(_get_qualname(ob)); + if (class_name.isNull()) { + Py_FatalError("Signature: missing class name in GetTypeKey"); + return nullptr; + } + return Py_BuildValue("(OO)", module_name.object(), class_name.object()); +} + +static PyObject *empty_dict = nullptr; + +PyObject *TypeKey_to_PropsDict(PyObject *type_key, PyObject *obtype) +{ + PyObject *dict = PyDict_GetItem(pyside_globals->arg_dict, type_key); + if (dict == nullptr) { + if (empty_dict == nullptr) + empty_dict = PyDict_New(); + dict = empty_dict; + } + if (!PyDict_Check(dict)) + dict = PySide_BuildSignatureProps(type_key); + return dict; +} + +static PyObject *_GetSignature_Cached(PyObject *props, PyObject *func_kind, PyObject *modifier) +{ + // Special case: We want to know the func_kind. + if (modifier) { +#if PY_VERSION_HEX >= 0x03000000 + PyUnicode_InternInPlace(&modifier); +#else + PyString_InternInPlace(&modifier); +#endif + if (modifier == PyMagicName::func_kind()) + return Py_BuildValue("O", func_kind); + } + + AutoDecRef key(modifier == nullptr ? Py_BuildValue("O", func_kind) + : Py_BuildValue("(OO)", func_kind, modifier)); + PyObject *value = PyDict_GetItem(props, key); + if (value == nullptr) { + // we need to compute a signature object + value = CreateSignature(props, key); + if (value != nullptr) { + if (PyDict_SetItem(props, key, value) < 0) + // this is an error + return nullptr; + } + else { + // key not found + Py_RETURN_NONE; + } + } + return Py_INCREF(value), value; +} + +PyObject *GetSignature_Function(PyObject *obfunc, PyObject *modifier) +{ + // make sure that we look into PyCFunction, only... + if (Py_TYPE(obfunc) == PepFunction_TypePtr) + Py_RETURN_NONE; + AutoDecRef obtype_mod(GetClassOrModOf(obfunc)); + AutoDecRef type_key(GetTypeKey(obtype_mod)); + if (type_key.isNull()) + Py_RETURN_NONE; + PyObject *dict = TypeKey_to_PropsDict(type_key, obtype_mod); + if (dict == nullptr) + return nullptr; + AutoDecRef func_name(PyObject_GetAttr(obfunc, PyMagicName::name())); + PyObject *props = !func_name.isNull() ? PyDict_GetItem(dict, func_name) : nullptr; + if (props == nullptr) + Py_RETURN_NONE; + + int flags = PyCFunction_GET_FLAGS(obfunc); + PyObject *func_kind; + if (PyModule_Check(obtype_mod)) + func_kind = PyName::function(); + else if (flags & METH_CLASS) + func_kind = PyName::classmethod(); + else if (flags & METH_STATIC) + func_kind = PyName::staticmethod(); + else + func_kind = PyName::method(); + return _GetSignature_Cached(props, func_kind, modifier); +} + +PyObject *GetSignature_Wrapper(PyObject *ob, PyObject *modifier) +{ + AutoDecRef func_name(PyObject_GetAttr(ob, PyMagicName::name())); + AutoDecRef objclass(PyObject_GetAttr(ob, PyMagicName::objclass())); + AutoDecRef class_key(GetTypeKey(objclass)); + if (func_name.isNull() || objclass.isNull() || class_key.isNull()) + return nullptr; + PyObject *dict = TypeKey_to_PropsDict(class_key, objclass); + if (dict == nullptr) + return nullptr; + PyObject *props = PyDict_GetItem(dict, func_name); + if (props == nullptr) + Py_RETURN_NONE; + return _GetSignature_Cached(props, PyName::method(), modifier); +} + +PyObject *GetSignature_TypeMod(PyObject *ob, PyObject *modifier) +{ + AutoDecRef ob_name(PyObject_GetAttr(ob, PyMagicName::name())); + AutoDecRef ob_key(GetTypeKey(ob)); + + PyObject *dict = TypeKey_to_PropsDict(ob_key, ob); + if (dict == nullptr) + return nullptr; + PyObject *props = PyDict_GetItem(dict, ob_name); + if (props == nullptr) + Py_RETURN_NONE; + return _GetSignature_Cached(props, PyName::method(), modifier); +} + +//////////////////////////////////////////////////////////////////////////// +// +// get_signature -- providing a superior interface +// +// Additional to the interface via `__signature__`, we also provide +// a general function, which allows for different signature layouts. +// The `modifier` argument is a string that is passed in from `loader.py`. +// Configuration what the modifiers mean is completely in Python. +// + +PyObject *get_signature_intern(PyObject *ob, PyObject *modifier) +{ + if (PyType_IsSubtype(Py_TYPE(ob), &PyCFunction_Type)) + return pyside_cf_get___signature__(ob, modifier); + if (Py_TYPE(ob) == PepStaticMethod_TypePtr) + return pyside_sm_get___signature__(ob, modifier); + if (Py_TYPE(ob) == PepMethodDescr_TypePtr) + return pyside_md_get___signature__(ob, modifier); + if (PyType_Check(ob)) + return pyside_tp_get___signature__(ob, modifier); + if (Py_TYPE(ob) == &PyWrapperDescr_Type) + return pyside_wd_get___signature__(ob, modifier); + return nullptr; +} + +static PyObject *get_signature(PyObject * /* self */, PyObject *args) +{ + PyObject *ob; + PyObject *modifier = nullptr; + + init_module_1(); + + if (!PyArg_ParseTuple(args, "O|O", &ob, &modifier)) + return nullptr; + if (Py_TYPE(ob) == PepFunction_TypePtr) + Py_RETURN_NONE; + PyObject *ret = get_signature_intern(ob, modifier); + if (ret != nullptr) + return ret; + Py_RETURN_NONE; +} + +PyMethodDef signature_methods[] = { + {"get_signature", (PyCFunction)get_signature, METH_VARARGS, + "get the __signature__, but pass an optional string parameter"}, + {nullptr, nullptr} +}; + +//////////////////////////////////////////////////////////////////////////// +// +// Argument Handling +// ----------------- +// +// * PySide_BuildSignatureArgs +// +// Called during class or module initialization. +// The signature strings from the C modules are stored in a dict for +// later use. +// +// * PySide_BuildSignatureProps +// +// Called on demand during signature retieval. This function calls all the way +// through `parser.py` and prepares all properties for the functions of the class. +// The parsed properties can then be used to create signature objects. +// + +static int PySide_BuildSignatureArgs(PyObject *obtype_mod, const char *signatures[]) +{ + init_module_1(); + AutoDecRef type_key(GetTypeKey(obtype_mod)); + /* + * PYSIDE-996: Avoid string overflow in MSVC, which has a limit of + * 2**15 unicode characters (64 K memory). + * Instead of one huge string, we take a ssize_t that is the + * address of a string array. It will not be turned into a real + * string list until really used by Python. This is quite optimal. + */ + AutoDecRef numkey(Py_BuildValue("n", signatures)); + if (type_key.isNull() || numkey.isNull() + || PyDict_SetItem(pyside_globals->arg_dict, type_key, numkey) < 0) + return -1; + /* + * We record also a mapping from type key to type/module. This helps to + * lazily initialize the Py_LIMITED_API in name_key_to_func(). + */ + return PyDict_SetItem(pyside_globals->map_dict, type_key, obtype_mod) == 0 ? 0 : -1; +} + +PyObject *PySide_BuildSignatureProps(PyObject *type_key) +{ + /* + * Here is the second part of the function. + * This part will be called on-demand when needed by some attribute. + * We simply pick up the arguments that we stored here and replace + * them by the function result. + */ + init_module_2(); + if (type_key == nullptr) + return nullptr; + PyObject *numkey = PyDict_GetItem(pyside_globals->arg_dict, type_key); + AutoDecRef strings(_address_to_stringlist(numkey)); + if (strings.isNull()) + return nullptr; + AutoDecRef arg_tup(Py_BuildValue("(OO)", type_key, strings.object())); + if (arg_tup.isNull()) + return nullptr; + PyObject *dict = PyObject_CallObject(pyside_globals->pyside_type_init_func, arg_tup); + if (dict == nullptr) { + if (PyErr_Occurred()) + return nullptr; + // No error: return an empty dict. + if (empty_dict == nullptr) + empty_dict = PyDict_New(); + return empty_dict; + } + // We replace the arguments by the result dict. + if (PyDict_SetItem(pyside_globals->arg_dict, type_key, dict) < 0) + return nullptr; + return dict; +} +// +//////////////////////////////////////////////////////////////////////////// + +static int PySide_FinishSignatures(PyObject *module, const char *signatures[]) +{ + /* + * Initialization of module functions and resolving of static methods. + */ + const char *name = PyModule_GetName(module); + if (name == nullptr) + return -1; + + // we abuse the call for types, since they both have a __name__ attribute. + if (PySide_BuildSignatureArgs(module, signatures) < 0) + return -1; + + /* + * Note: This function crashed when called from PySide_BuildSignatureArgs. + * Probably this was an import timing problem. + * + * Pep384: We need to switch this always on since we have no access + * to the PyCFunction attributes. Therefore I simplified things + * and always use our own mapping. + */ + PyObject *key, *func, *obdict = PyModule_GetDict(module); + Py_ssize_t pos = 0; + + while (PyDict_Next(obdict, &pos, &key, &func)) + if (PyCFunction_Check(func)) + if (PyDict_SetItem(pyside_globals->map_dict, func, module) < 0) + return -1; + if (_finish_nested_classes(obdict) < 0) + return -1; + // The finish_import function will not work the first time since phase 2 + // was not yet run. But that is ok, because the first import is always for + // the shiboken module (or a test module). + if (pyside_globals->finish_import_func == nullptr) { + assert(strncmp(name, "PySide2.", 8) != 0); + return 0; + } + AutoDecRef ret(PyObject_CallFunction( + pyside_globals->finish_import_func, const_cast("(O)"), module)); + return ret.isNull() ? -1 : 0; +} + +//////////////////////////////////////////////////////////////////////////// +// +// External functions interface +// +// These are exactly the supported functions from `signature.h`. +// + +int InitSignatureStrings(PyTypeObject *type, const char *signatures[]) +{ + auto *ob_type = reinterpret_cast(type); + int ret = PySide_BuildSignatureArgs(ob_type, signatures); + if (ret < 0) { + PyErr_Print(); + PyErr_SetNone(PyExc_ImportError); + } + return ret; +} + +void FinishSignatureInitialization(PyObject *module, const char *signatures[]) +{ + /* + * This function is called at the very end of a module initialization. + * We now patch certain types to support the __signature__ attribute, + * initialize module functions and resolve static methods. + * + * Still, it is not possible to call init phase 2 from here, + * because the import is still running. Do it from Python! + */ + if ( PySide_PatchTypes() < 0 + || PySide_FinishSignatures(module, signatures) < 0) { + PyErr_Print(); + PyErr_SetNone(PyExc_ImportError); + } +} + +void SetError_Argument(PyObject *args, const char *func_name) +{ + /* + * This function replaces the type error construction with extra + * overloads parameter in favor of using the signature module. + * Error messages are rare, so we do it completely in Python. + */ + init_module_1(); + init_module_2(); + AutoDecRef res(PyObject_CallFunction(pyside_globals->seterror_argument_func, + const_cast("(Os)"), args, func_name)); + if (res.isNull()) { + PyErr_Print(); + Py_FatalError("seterror_argument did not receive a result"); + } + PyObject *err, *msg; + if (!PyArg_UnpackTuple(res, func_name, 2, 2, &err, &msg)) { + PyErr_Print(); + Py_FatalError("unexpected failure in seterror_argument"); + } + PyErr_SetObject(err, msg); +} + +/* + * Support for the metatype SbkObjectType_Type's tp_getset. + * + * This was not necessary for __signature__, because PyType_Type inherited it. + * But the __doc__ attribute existed already by inheritance, and calling + * PyType_Modified() is not supported. So we added the getsets explicitly + * to the metatype. + */ + +PyObject *Sbk_TypeGet___signature__(PyObject *ob, PyObject *modifier) +{ + return pyside_tp_get___signature__(ob, modifier); +} + +PyObject *Sbk_TypeGet___doc__(PyObject *ob) +{ + return pyside_tp_get___doc__(ob); +} + +PyObject *GetFeatureDict() +{ + init_module_1(); + return pyside_globals->feature_dict; +} + +} //extern "C" diff --git a/sources/shiboken2/libshiboken/signature/signature_doc.rst b/sources/shiboken2/libshiboken/signature/signature_doc.rst new file mode 100644 index 000000000..f3dc652b6 --- /dev/null +++ b/sources/shiboken2/libshiboken/signature/signature_doc.rst @@ -0,0 +1,357 @@ +************************* +The signature C extension +************************* + +This module is a C extension for CPython 3.5 and up, and CPython 2.7. +Its purpose is to provide support for the ``__signature__`` attribute +of builtin PyCFunction objects. + + +Short Introduction to the Topic +=============================== + +Beginning with CPython 3.5, Python functions began to grow a ``__signature__`` +attribute for normal Python functions. This is totally optional and just +a nice-to-have feature in Python. + +PySide, on the other hand, could use ``__signature__`` very much, because the +typing info for the 15000+ PySide functions is really missing, and it +would be nice to have this info directly available. + + +The Idea to Support Signatures +============================== + +We want to have an additional ``__signature__`` attribute in all PySide +methods, without changing lots of generated code. +Therefore, we did not change any of the existing data structures, +but supported the new attribute by a global dictionary. + +When the ``__signature__`` property is requested, a method is called that +does a lookup in the global dict. This is a flexible approach with little impact +to the rest of the project. It has very limited overhead compared to direct +attribute access, but for the need of a signature access from time to time, +this is an adequate compromise. + + +How this Code Works +------------------- + +Signatures are supported for regular Python functions, only. Creating signatures +for ``PyCFunction`` objects would require quite some extra effort in Python. + +Fortunately, we found this special *stealth* technique, that saves us most of the +needed effort: + +The basic idea is to create a dummy Python function with **varnames**, **defaults** +and **annotations** properties, and then to use the inspect +module to create a signature object. This object is returned as the computed +result of the ``__signature__`` attribute of the real ``PyCFunction`` object. + +There is one thing that really changes Python a bit: + +* We added the ``__signature__`` attribute to every function. + +That is a little change to Python that does not harm, but it saves us +tons of code, that was needed in the early versions of the module. + +The internal work is done in two steps: + +* All functions of a class get the *signature text* when the module is imported. + This is only a very small overhead added to the startup time. It is a single + string for each whole class. +* The actual signature object is created later, when the attribute is really + requested. Signatures are cached and only created on first access. + +Example: + +The ``PyCFunction`` ``QtWidgets.QApplication.palette`` is interrogated for its +signature. That means ``pyside_sm_get___signature__()`` is called. +It calls ``GetSignature_Function`` which returns the signature if it is found. + + +Why this Code is Fast +--------------------- + +It costs a little time (maybe 6 seconds) to run through every single signature +object, since these are more than 25000 Python objects. But all the signature +objects will be rarely accessed but in special applications. +The normal case are only a few accesses, and these are working pretty fast. + +The key to make this signature module fast is to avoid computation as much as +possible. When no signature objects are used, then almost no time is lost in +initialization. Only the above mentioned strings and some support modules are +additionally loaded on ``import PySide2``. +When it comes to signature usage, then late initialization is used and cached. +This technique is also known as *full laziness* in haskell. + +There are actually two locations where late initialization occurs: + +* ``dict`` can be no dict but a tuple. That is the initial argument tuple that + was saved by ``PySide_BuildSignatureArgs`` at module load time. + If so, then ``pyside_type_init`` in parser.py will be called, + which parses the string and creates the dict. +* ``props`` can be empty. Then ``create_signature`` in loader.py + is called, which uses a dummy function to produce a signature instance + with the inspect module. + +The initialization that is always done is just two dictionary writes +per class, and we have about 1000 classes. +To measure the additional overhead, we have simulated what happens +when ``from PySide2 import *`` is performed. +It turned out that the overhead is below 0.5 ms. + + +The Signature Package Structure +------------------------------- + +The C++ code involved with the signature module is completely in the file +shiboken2/libshiboken/signature.cpp . All other functionality is implemented in +the ``signature`` Python package. It has the following structure:: + + shiboken2/files.dir/shibokensupport/ + backport_inspect.py + + signature/ + loader.py + parser.py + mapping.py + errorhandler.py + layout.py + + lib/ + enum_sig.py + tool.py + + + +Really important are the **parser**, **mapping**, **errorhandler**, **enum_sig**, +**layout** and **loader** modules. The rest is needed to create Python 2 compatibility +or be compatible with embedding and installers. + + +loader.py +~~~~~~~~~ + +This module assembles and imports the ``inspect`` module, and then exports the +``create_signature`` function. This function takes a fake function and some +attributes and builds a ``__signature__`` object with the inspect module. + + +parser.py +~~~~~~~~~ + +This module takes a class signatures string from C++ and parses it into the +needed properties for the ``create_signature`` function. Its entry point is the +``pyside_type_init`` function, which is called from the C module via ``loader.py``. + + +mapping.py +~~~~~~~~~~ + +The purpose of the mapping module is maintaining a list of replacement strings +that map from the *signature text* in C to the property strings that Python +needs. A lot of mappings are resolved by rather complex expressions in ``parser.py``, +but a few hundred cases are better to spell explicitly, here. + + +errorhandler.py +~~~~~~~~~~~~~~~ + +Since ``Qt For Python 5.12``, we no longer use the builtin type error messages from C++. +Instead, we get much better results with the signature module. At the same time, +this enforced supporting shiboken as well, and the signature module was no longer +optional. + + +enum_sig.py +~~~~~~~~~~~ + +The diverse applications of the signature module all needed to iterate over modules, +classes and functions. In order to centralize this enumeration, the process has +been factored out as a context manager. The user has only to supply functions +that do the actual formatting. + +See for example the .pyi generator ``pyside2/PySide2/support/generate_pyi.py``. + + +layout.py +~~~~~~~~~ + +As more applications used the signature module, different formatting of signatures +was needed. To support that, we created the function ``create_signature``, which +has a parameter to choose from some prefefined layouts. + + +*typing27.py* +~~~~~~~~~~~~~ + +Python 2 has no typing module at all. This is a backport of the minimum that is needed. + + +*backport_inspect.py* +~~~~~~~~~~~~~~~~~~~~~ + +Python 2 has an inspect module, but lacks the signature functions, completely. +This module adds the missing functionality, which is merged at runtime into +the inspect module. + + +Multiple Arities +---------------- + +One aspect that was ignored so far was *multiple arities*: How to handle it when +a function has more than one signature? + +I did not find any note on how multiple signatures should be treated in Python, +but this simple rules seem to work well: + +* If there is a list, then it is a multi-signature. +* Otherwise, it is a simple signature. + + +Impacts of The Signature Module +=============================== + +The signature module has a number of impacts to other PySide modules, which were +created as a consequence of its existence, and there will be a few more in the +future: + + +existence_test.py +----------------- + +The file ``pyside2/tests/registry/existence_test.py`` was written using the +signatures from the signatures module. The idea is that there are some 15000 +functions with a certain signature. + +These functions should not get lost by some bad check-in. Therefore, a list +of all existing signatures is kept as a module that assembles a +dictionary. The function existence is checked, and also the exact arity. + +This module exists for every PySide release and every platform. The initial +module is generated once and saved as ``exists_{plat}_{version}.py``. + +An error is normally only reported as a warning, but: + + +Interaction With The Coin Module +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When this test program is run in COIN, then the warnings are turned into +errors. The reason is that only in COIN, we have a stable configuration +of PySide modules that can reliably be compared. + +These modules have the name ``exists_{platf}_{version}_ci.py``, and as a big +exception for generated code, these files are *intentionally* checked in. + + +What Happens When a List is Missing? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When a new version of PySide gets created, then the existence test files +initially do not exist. + +When a COIN test is run, then it will complain about the error and create +the missing module on standard output. +But since COIN tests are run multiple times, the output that was generated +by the first test will still exist at the subsequent runs. +(If COIN was properly implemented, we could not take that advantage and +would need to implement that as an extra exception.) + +As a result, a missing module will be reported as a test which partially +succeeded (called "FLAKY"). To avoid further flaky tests and to activate as a real test, +we can now capture the error output of COIN and check the generated module +in. + + +Explicitly Enforcing Recreation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The former way to regenerate the registry files was to remove the files +and check that in. This has the desired effect, but creates huge deltas. +As a more efficient way, we have prepared a comment in the first line +that contains the word "recreate". +By uncommenting this line, a NameError is triggered, which has the same +effect. + + +init_platform.py +~~~~~~~~~~~~~~~~ + +For generating the ``exists_{platf}_{version}`` modules, the module +``pyside2/tests/registry/init_platform.py`` was written. It can be used +standalone from the commandline, to check the compatibility of some +changes, directly. + + +scrape_testresults.py +--------------------- + +To simplify and automate the process of extracting the ``exists_{platf}_{version}_ci.py`` +files, the script ``pyside2/tests/registry/scrape_testresults.py`` has been written. + +This script scans the whole testresults website for PySide, that is:: + + https://testresults.qt.io/coin/api/results/pyside/pyside-setup/ + +On the first scan, the script runs less than 30 minutes. After that, a cache +is generated and the scan works *much* faster. The test results are placed +into the folder ``pyside2/tests/registry/testresults/embedded/`` with a +unique name that allows for easy sorting. Example:: + + testresults/embedded/2018_09_10_10_40_34-test_1536891759-exists_linux_5_11_2_ci.py + +These files are created only once. If they already exist, they are not touched, again. +The file `pyside2/tests/registry/known_urls.json`` holds all scanned URLs after +a successful scan. The ``testresults/embedded`` folder can be kept for reference +or can be removed. Important is only the json file. + +The result of a scan is then directly placed into the ``pyside2/tests/registry/`` +folder. It should be reviewed and then eventually checked in. + + +generate_pyi.py +--------------- + +``pyside2/PySide2/support/generate_pyi.py`` is still under development. +This module generates so-called hinting stubs for integration of PySide +with diverse *Python IDEs*. + +Although this module creates the stubs as an add-on, the +impact on the quality of the signature module is considerable: + +The module must create syntactically correct ``.pyi`` files which contain +not only signatures but also constants and enums of all PySide modules. +This serves as an extra challenge that has a very positive effect on +the completeness and correctness of signatures. + + +Current Extensions +------------------ + +Before the signature module was written, there already existed the concept of +signatures, but in a more C++ - centric way. From that time, there existed +the error messages, which are created when a function gets wrong argument types. + +These error messages were replaced by text generated on demand by +the signature module, in order to be more consistent and correct. +This was implemented in ``Qt For Python 5.12.0``. + +Additionally, the ``__doc__`` attribute of PySide methods was not set. +It was easy to get a nice ``help()`` feature by creating signatures +as default content for docstrings. +This was implemented in ``Qt For Python 5.12.1``. + + +Literature +========== + + `PEP 362 – Function Signature Object `__ + + `PEP 484 – Type Hints `__ + + `PEP 3107 – Function Annotations `__ + + +*Personal Remark: This module is dedicated to our lovebird "Püppi", who died on 2017-09-15.* diff --git a/sources/shiboken2/libshiboken/signature/signature_extend.cpp b/sources/shiboken2/libshiboken/signature/signature_extend.cpp new file mode 100644 index 000000000..1490a6003 --- /dev/null +++ b/sources/shiboken2/libshiboken/signature/signature_extend.cpp @@ -0,0 +1,294 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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$ +** +****************************************************************************/ + +//////////////////////////////////////////////////////////////////////////// +// +// signature_extend.cpp +// -------------------- +// +// This file contains the additions and changes to the following +// Python types: +// +// PyMethodDescr_Type +// PyCFunction_Type +// PyStaticMethod_Type +// PyType_Type +// PyWrapperDescr_Type +// +// Their `tp_getset` fields are modified so support the `__signature__` +// attribute and additions to the `__doc__` attribute. +// + +#include "autodecref.h" +#include "sbkstring.h" +#include "sbkstaticstrings.h" +#include "sbkstaticstrings_p.h" + +#include "signature_p.h" + +using namespace Shiboken; + +extern "C" { + +typedef PyObject *(*signaturefunc)(PyObject *, PyObject *); + +static PyObject *_get_written_signature(signaturefunc sf, PyObject *ob, PyObject *modifier) +{ + /* + * Be a writable Attribute, but have a computed value. + * + * If a signature has not been written, call the signature function. + * If it has been written, return the written value. + * After __del__ was called, the function value re-appears. + * + * Note: This serves also for the new version that does not allow any + * assignment if we have a computed value. We only need to check if + * a computed value exists and then forbid writing. + * See pyside_set___signature + */ + PyObject *ret = PyDict_GetItem(pyside_globals->value_dict, ob); + if (ret == nullptr) + return ob == nullptr ? nullptr : sf(ob, modifier); + Py_INCREF(ret); + return ret; +} + +PyObject *pyside_cf_get___signature__(PyObject *func, PyObject *modifier) +{ + init_module_2(); + return _get_written_signature(GetSignature_Function, func, modifier); +} + +PyObject *pyside_sm_get___signature__(PyObject *sm, PyObject *modifier) +{ + init_module_2(); + AutoDecRef func(PyObject_GetAttr(sm, PyMagicName::func())); + if (Py_TYPE(func) == PepFunction_TypePtr) + return PyObject_GetAttr(func, PyMagicName::signature()); + return _get_written_signature(GetSignature_Function, func, modifier); +} + +PyObject *pyside_md_get___signature__(PyObject *ob_md, PyObject *modifier) +{ + init_module_2(); + AutoDecRef func(name_key_to_func(ob_md)); + if (func.object() == Py_None) + return Py_None; + if (func.isNull()) + Py_FatalError("missing mapping in MethodDescriptor"); + return pyside_cf_get___signature__(func, modifier); +} + +PyObject *pyside_wd_get___signature__(PyObject *ob, PyObject *modifier) +{ + init_module_2(); + return _get_written_signature(GetSignature_Wrapper, ob, modifier); +} + +PyObject *pyside_tp_get___signature__(PyObject *obtype_mod, PyObject *modifier) +{ + init_module_2(); + return _get_written_signature(GetSignature_TypeMod, obtype_mod, modifier); +} + +//////////////////////////////////////////////////////////////////////////// +// +// Augmenting builtin types with a __signature__ attribute. +// +// This is a harmless change to Python, similar like __text_signature__. +// We could avoid it, but then we would need to copy quite some module +// initialization functions which are pretty version- and word size +// dependent. I think this little patch is the lesser of the two evils. +// +// Please note that in fact we are modifying 'type', the metaclass of all +// objects, because we add new functionality. +// +// Addendum 2019-01-12: We now also compute a docstring from the signature. +// + +// keep the original __doc__ functions +static PyObject *old_cf_doc_descr = nullptr; +static PyObject *old_sm_doc_descr = nullptr; +static PyObject *old_md_doc_descr = nullptr; +static PyObject *old_tp_doc_descr = nullptr; +static PyObject *old_wd_doc_descr = nullptr; + +static int handle_doc_in_progress = 0; + +static PyObject *handle_doc(PyObject *ob, PyObject *old_descr) +{ + init_module_1(); + init_module_2(); + AutoDecRef ob_type_mod(GetClassOrModOf(ob)); + const char *name; + if (PyModule_Check(ob_type_mod)) + name = PyModule_GetName(ob_type_mod); + else + name = reinterpret_cast(ob_type_mod.object())->tp_name; + if (handle_doc_in_progress || name == nullptr + || strncmp(name, "PySide2.", 8) != 0) + return PyObject_CallMethodObjArgs(old_descr, + PyMagicName::get(), + ob, nullptr); + handle_doc_in_progress++; + PyObject *res = PyObject_CallFunction( + pyside_globals->make_helptext_func, + const_cast("(O)"), ob); + handle_doc_in_progress--; + if (res == nullptr) { + PyErr_Print(); + Py_FatalError("handle_doc did not receive a result"); + } + return res; +} + +static PyObject *pyside_cf_get___doc__(PyObject *cf) +{ + return handle_doc(cf, old_cf_doc_descr); +} + +static PyObject *pyside_sm_get___doc__(PyObject *sm) +{ + return handle_doc(sm, old_sm_doc_descr); +} + +static PyObject *pyside_md_get___doc__(PyObject *md) +{ + return handle_doc(md, old_md_doc_descr); +} + +PyObject *pyside_tp_get___doc__(PyObject *tp) +{ + return handle_doc(tp, old_tp_doc_descr); +} + +static PyObject *pyside_wd_get___doc__(PyObject *wd) +{ + return handle_doc(wd, old_wd_doc_descr); +} + +// the default setter for all objects +static int pyside_set___signature__(PyObject *op, PyObject *value) +{ + // By this additional check, this function refuses write access. + // We consider both nullptr and Py_None as not been written. + AutoDecRef has_val(get_signature_intern(op, nullptr)); + if (!(has_val.isNull() || has_val == Py_None)) { + PyErr_Format(PyExc_AttributeError, + "Attribute '__signature__' of '%.50s' object is not writable", + Py_TYPE(op)->tp_name); + return -1; + } + int ret = value == nullptr ? PyDict_DelItem(pyside_globals->value_dict, op) + : PyDict_SetItem(pyside_globals->value_dict, op, value); + Py_XINCREF(value); + return ret; +} + +static PyGetSetDef new_PyCFunction_getsets[] = { + {const_cast("__doc__"), (getter)pyside_cf_get___doc__}, + {const_cast("__signature__"), (getter)pyside_cf_get___signature__, + (setter)pyside_set___signature__}, + {nullptr} +}; + +static PyGetSetDef new_PyStaticMethod_getsets[] = { + {const_cast("__doc__"), (getter)pyside_sm_get___doc__}, + {const_cast("__signature__"), (getter)pyside_sm_get___signature__, + (setter)pyside_set___signature__}, + {nullptr} +}; + +static PyGetSetDef new_PyMethodDescr_getsets[] = { + {const_cast("__doc__"), (getter)pyside_md_get___doc__}, + {const_cast("__signature__"), (getter)pyside_md_get___signature__, + (setter)pyside_set___signature__}, + {nullptr} +}; + +static PyGetSetDef new_PyType_getsets[] = { + {const_cast("__doc__"), (getter)pyside_tp_get___doc__}, + {const_cast("__signature__"), (getter)pyside_tp_get___signature__, + (setter)pyside_set___signature__}, + {nullptr} +}; + +static PyGetSetDef new_PyWrapperDescr_getsets[] = { + {const_cast("__doc__"), (getter)pyside_wd_get___doc__}, + {const_cast("__signature__"), (getter)pyside_wd_get___signature__, + (setter)pyside_set___signature__}, + {nullptr} +}; + +int PySide_PatchTypes(void) +{ + static int init_done = 0; + + if (!init_done) { + AutoDecRef meth_descr(PyObject_GetAttrString( + reinterpret_cast(&PyString_Type), "split")); + AutoDecRef wrap_descr(PyObject_GetAttrString( + reinterpret_cast(Py_TYPE(Py_True)), "__add__")); + // abbreviations for readability + auto md_gs = new_PyMethodDescr_getsets; + auto md_doc = &old_md_doc_descr; + auto cf_gs = new_PyCFunction_getsets; + auto cf_doc = &old_cf_doc_descr; + auto sm_gs = new_PyStaticMethod_getsets; + auto sm_doc = &old_sm_doc_descr; + auto tp_gs = new_PyType_getsets; + auto tp_doc = &old_tp_doc_descr; + auto wd_gs = new_PyWrapperDescr_getsets; + auto wd_doc = &old_wd_doc_descr; + + if (meth_descr.isNull() || wrap_descr.isNull() + || PyType_Ready(Py_TYPE(meth_descr)) < 0 + || add_more_getsets(PepMethodDescr_TypePtr, md_gs, md_doc) < 0 + || add_more_getsets(&PyCFunction_Type, cf_gs, cf_doc) < 0 + || add_more_getsets(PepStaticMethod_TypePtr, sm_gs, sm_doc) < 0 + || add_more_getsets(&PyType_Type, tp_gs, tp_doc) < 0 + || add_more_getsets(Py_TYPE(wrap_descr), wd_gs, wd_doc) < 0 + ) + return -1; + init_done = 1; + } + return 0; +} + +} // extern "C" diff --git a/sources/shiboken2/libshiboken/signature/signature_globals.cpp b/sources/shiboken2/libshiboken/signature/signature_globals.cpp new file mode 100644 index 000000000..6af64682e --- /dev/null +++ b/sources/shiboken2/libshiboken/signature/signature_globals.cpp @@ -0,0 +1,295 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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$ +** +****************************************************************************/ + +//////////////////////////////////////////////////////////////////////////// +// +// signature_global.cpp +// +// This file contains the global data structures and init code. +// + +#include "autodecref.h" +#include "sbkstring.h" +#include "sbkstaticstrings.h" +#include "sbkstaticstrings_p.h" + +#include "signature_p.h" + +using namespace Shiboken; + +extern "C" { + +static const char *PySide_CompressedSignaturePackage[] = { +#include "embed/signature_inc.h" + }; + +static const unsigned char PySide_SignatureLoader[] = { +#include "embed/signature_bootstrap_inc.h" + }; + +static PyObject *_init_pyside_extension(PyObject * /* self */, PyObject * /* args */) +{ + init_module_1(); + init_module_2(); + Py_RETURN_NONE; +} + +// This function will be inserted into __builtins__. +static PyMethodDef init_methods[] = { + {"_init_pyside_extension", (PyCFunction)_init_pyside_extension, METH_NOARGS}, + {nullptr, nullptr} +}; + +static safe_globals_struc *init_phase_1(PyMethodDef *init_meth) +{ + { + auto *p = reinterpret_cast + (malloc(sizeof(safe_globals_struc))); + if (p == nullptr) + goto error; + /* + * Initializing module signature_bootstrap. + * Since we now have an embedding script, we can do this without any + * Python strings in the C code. + */ +#ifdef Py_LIMITED_API + // We must work for multiple versions, so use source code. +#else + AutoDecRef marshal_module(PyImport_Import(PyName::marshal())); + if (marshal_module.isNull()) + goto error; + AutoDecRef loads(PyObject_GetAttr(marshal_module, PyName::loads())); + if (loads.isNull()) + goto error; +#endif + char *bytes_cast = reinterpret_cast( + const_cast(PySide_SignatureLoader)); + AutoDecRef bytes(PyBytes_FromStringAndSize(bytes_cast, sizeof(PySide_SignatureLoader))); + if (bytes.isNull()) + goto error; +#ifdef Py_LIMITED_API + PyObject *builtins = PyEval_GetBuiltins(); + PyObject *compile = PyDict_GetItem(builtins, PyName::compile()); + if (compile == nullptr) + goto error; + AutoDecRef code_obj(PyObject_CallFunction(compile, "Oss", + bytes.object(), "(builtin)", "exec")); +#else + AutoDecRef code_obj(PyObject_CallFunctionObjArgs( + loads, bytes.object(), nullptr)); +#endif + if (code_obj.isNull()) + goto error; + p->helper_module = PyImport_ExecCodeModule(const_cast + ("signature_bootstrap"), code_obj); + if (p->helper_module == nullptr) + goto error; + // Initialize the module + PyObject *mdict = PyModule_GetDict(p->helper_module); + if (PyDict_SetItem(mdict, PyMagicName::builtins(), PyEval_GetBuiltins()) < 0) + goto error; + /* + * Unpack an embedded ZIP file with more signature modules. + * They will be loaded later with the zipimporter. + * Due to MSVC's limitation to 64k strings, we need to assemble pieces. + */ + const char **block_ptr = (const char **)PySide_CompressedSignaturePackage; + int npieces = 0; + PyObject *piece, *zipped_string_sequence = PyList_New(0); + if (zipped_string_sequence == nullptr) + return nullptr; + for (; **block_ptr != 0; ++block_ptr) { + npieces++; + // we avoid the string/unicode dilemma by not using PyString_XXX: + piece = Py_BuildValue("s", *block_ptr); + if (piece == nullptr || PyList_Append(zipped_string_sequence, piece) < 0) + goto error; + } + if (PyDict_SetItemString(mdict, "zipstring_sequence", zipped_string_sequence) < 0) + goto error; + Py_DECREF(zipped_string_sequence); + + // build a dict for diverse mappings + p->map_dict = PyDict_New(); + if (p->map_dict == nullptr) + goto error; + + // build a dict for the prepared arguments + p->arg_dict = PyDict_New(); + if (p->arg_dict == nullptr + || PyObject_SetAttrString(p->helper_module, "pyside_arg_dict", p->arg_dict) < 0) + goto error; + + // build a dict for assigned signature values + p->value_dict = PyDict_New(); + if (p->value_dict == nullptr) + goto error; + + // PYSIDE-1019: build a __feature__ dict + p->feature_dict = PyDict_New(); + if (p->feature_dict == nullptr + || PyObject_SetAttrString(p->helper_module, "pyside_feature_dict", p->feature_dict) < 0) + goto error; + + // This function will be disabled until phase 2 is done. + p->finish_import_func = nullptr; + + // Initialize the explicit init function. + AutoDecRef init(PyCFunction_NewEx(init_meth, nullptr, nullptr)); + if (init.isNull() + || PyDict_SetItemString(PyEval_GetBuiltins(), init_meth->ml_name, init) != 0) + goto error; + + return p; + } +error: + PyErr_Print(); + Py_FatalError("could not initialize part 1"); + return nullptr; +} + +static int init_phase_2(safe_globals_struc *p, PyMethodDef *methods) +{ + { + PyMethodDef *ml; + + // The single function to be called, but maybe more to come. + for (ml = methods; ml->ml_name != nullptr; ml++) { + PyObject *v = PyCFunction_NewEx(ml, nullptr, nullptr); + if (v == nullptr + || PyObject_SetAttrString(p->helper_module, ml->ml_name, v) != 0) + goto error; + Py_DECREF(v); + } + PyObject *bootstrap_func = PyObject_GetAttrString(p->helper_module, "bootstrap"); + if (bootstrap_func == nullptr) + goto error; + // The return value of the bootstrap function is the loader module. + PyObject *loader = PyObject_CallFunction(bootstrap_func, const_cast("()")); + if (loader == nullptr) + goto error; + // now the loader should be initialized + p->pyside_type_init_func = PyObject_GetAttrString(loader, "pyside_type_init"); + if (p->pyside_type_init_func == nullptr) + goto error; + p->create_signature_func = PyObject_GetAttrString(loader, "create_signature"); + if (p->create_signature_func == nullptr) + goto error; + p->seterror_argument_func = PyObject_GetAttrString(loader, "seterror_argument"); + if (p->seterror_argument_func == nullptr) + goto error; + p->make_helptext_func = PyObject_GetAttrString(loader, "make_helptext"); + if (p->make_helptext_func == nullptr) + goto error; + p->finish_import_func = PyObject_GetAttrString(loader, "finish_import"); + if (p->finish_import_func == nullptr) + goto error; + return 0; + } +error: + PyErr_Print(); + Py_FatalError("could not initialize part 2"); + return -1; +} + +#ifndef _WIN32 +//////////////////////////////////////////////////////////////////////////// +// a stack trace for linux-like platforms +#include +#if defined(__GLIBC__) +# include +#endif +#include +#include +#include + +static void handler(int sig) { +#if defined(__GLIBC__) + void *array[30]; + size_t size; + + // get void *'s for all entries on the stack + size = backtrace(array, 30); + + // print out all the frames to stderr +#endif + fprintf(stderr, "Error: signal %d:\n", sig); +#if defined(__GLIBC__) + backtrace_symbols_fd(array, size, STDERR_FILENO); +#endif + exit(1); +} + +//////////////////////////////////////////////////////////////////////////// +#endif // _WIN32 + +safe_globals pyside_globals = nullptr; + +void init_module_1(void) +{ + static int init_done = 0; + + if (!init_done) { + pyside_globals = init_phase_1(init_methods); + if (pyside_globals != nullptr) + init_done = 1; + +#ifndef _WIN32 + // We enable the stack trace in CI, only. + const char *testEnv = getenv("QTEST_ENVIRONMENT"); + if (testEnv && strstr(testEnv, "ci")) + signal(SIGSEGV, handler); // install our handler +#endif // _WIN32 + + } +} + +void init_module_2(void) +{ + static int init_done = 0; + + if (!init_done) { + // Phase 2 will call __init__.py which touches a signature, itself. + // Therefore we set init_done prior to init_phase_2(). + init_done = 1; + init_phase_2(pyside_globals, signature_methods); + } +} + +} // extern "C" diff --git a/sources/shiboken2/libshiboken/signature/signature_helper.cpp b/sources/shiboken2/libshiboken/signature/signature_helper.cpp new file mode 100644 index 000000000..7e92a9861 --- /dev/null +++ b/sources/shiboken2/libshiboken/signature/signature_helper.cpp @@ -0,0 +1,364 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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$ +** +****************************************************************************/ + +//////////////////////////////////////////////////////////////////////////// +// +// signature_helper.cpp +// -------------------- +// +// This file contains assoerted helper functions that are needed, +// but it is not helpful to see them all the time. +// + +#include "autodecref.h" +#include "sbkstring.h" +#include "sbkstaticstrings.h" +#include "sbkstaticstrings_p.h" + +#include "signature_p.h" + +using namespace Shiboken; + +extern "C" { + +// Helper for __qualname__ which might not always exist in Python 2 (type). +PyObject *_get_qualname(PyObject *ob) +{ + // We support __qualname__ for types, only. + assert(PyType_Check(ob)); + PyObject *name = PyObject_GetAttr(ob, PyMagicName::qualname()); + if (name == nullptr) { + PyErr_Clear(); + name = PyObject_GetAttr(ob, PyMagicName::name()); + } + return name; +} + +static int _fixup_getset(PyTypeObject *type, const char *name, PyGetSetDef *new_gsp) +{ + /* + * This function pre-fills all fields of the new gsp. We then + * insert the changed values. + */ + PyGetSetDef *gsp = type->tp_getset; + if (gsp != nullptr) { + for (; gsp->name != nullptr; gsp++) { + if (strcmp(gsp->name, name) == 0) { + new_gsp->set = gsp->set; + new_gsp->doc = gsp->doc; + new_gsp->closure = gsp->closure; + return 1; // success + } + } + } + PyMemberDef *md = type->tp_members; + if (md != nullptr) + for (; md->name != nullptr; md++) + if (strcmp(md->name, name) == 0) + return 1; + // staticmethod has just a `__doc__` in the class + assert(strcmp(type->tp_name, "staticmethod") == 0 && strcmp(name, "__doc__") == 0); + return 0; +} + +int add_more_getsets(PyTypeObject *type, PyGetSetDef *gsp, PyObject **doc_descr) +{ + /* + * This function is used to assign a new `__signature__` attribute, + * and also to override a `__doc__` or `__name__` attribute. + */ + assert(PyType_Check(type)); + PyType_Ready(type); + PyObject *dict = type->tp_dict; + for (; gsp->name != nullptr; gsp++) { + PyObject *have_descr = PyDict_GetItemString(dict, gsp->name); + if (have_descr != nullptr) { + Py_INCREF(have_descr); + if (strcmp(gsp->name, "__doc__") == 0) + *doc_descr = have_descr; + else + assert(false); + if (!_fixup_getset(type, gsp->name, gsp)) + continue; + } + AutoDecRef descr(PyDescr_NewGetSet(type, gsp)); + if (descr.isNull()) + return -1; + if (PyDict_SetItemString(dict, gsp->name, descr) < 0) + return -1; + } + PyType_Modified(type); + return 0; +} + +static PyObject *get_funcname(PyObject *ob) +{ + PyObject *func = ob; + if (Py_TYPE(ob) == PepStaticMethod_TypePtr) + func = PyObject_GetAttr(ob, PyMagicName::func()); + else + Py_INCREF(func); + PyObject *func_name = PyObject_GetAttr(func, PyMagicName::name()); + Py_DECREF(func); + if (func_name == nullptr) + Py_FatalError("unexpected name problem in compute_name_key"); + return func_name; +} + +static PyObject *compute_name_key(PyObject *ob) +{ + if (PyType_Check(ob)) + return GetTypeKey(ob); + AutoDecRef func_name(get_funcname(ob)); + AutoDecRef type_key(GetTypeKey(GetClassOrModOf(ob))); + return Py_BuildValue("(OO)", type_key.object(), func_name.object()); +} + +static int build_name_key_to_func(PyObject *obtype) +{ + auto *type = reinterpret_cast(obtype); + PyMethodDef *meth = type->tp_methods; + + if (meth == nullptr) + return 0; + + AutoDecRef type_key(GetTypeKey(obtype)); + for (; meth->ml_name != nullptr; meth++) { + AutoDecRef func(PyCFunction_NewEx(meth, obtype, nullptr)); + AutoDecRef func_name(get_funcname(func)); + AutoDecRef name_key(Py_BuildValue("(OO)", type_key.object(), func_name.object())); + if (func.isNull() || name_key.isNull() + || PyDict_SetItem(pyside_globals->map_dict, name_key, func) < 0) + return -1; + } + return 0; +} + +PyObject *name_key_to_func(PyObject *ob) +{ + /* + * We build a mapping from name_key to function. + * This could also be computed directly, but the Limited API + * makes this impossible. So we always build our own mapping. + */ + AutoDecRef name_key(compute_name_key(ob)); + if (name_key.isNull()) + Py_RETURN_NONE; + + PyObject *ret = PyDict_GetItem(pyside_globals->map_dict, name_key); + if (ret == nullptr) { + // do a lazy initialization + AutoDecRef type_key(GetTypeKey(GetClassOrModOf(ob))); + PyObject *type = PyDict_GetItem(pyside_globals->map_dict, + type_key); + if (type == nullptr) + Py_RETURN_NONE; + assert(PyType_Check(type)); + if (build_name_key_to_func(type) < 0) + return nullptr; + ret = PyDict_GetItem(pyside_globals->map_dict, name_key); + } + Py_XINCREF(ret); + return ret; +} + +PyObject *_get_class_of_cf(PyObject *ob_cf) +{ + PyObject *selftype = PyCFunction_GET_SELF(ob_cf); + if (selftype == nullptr) { + selftype = PyDict_GetItem(pyside_globals->map_dict, ob_cf); + if (selftype == nullptr) { + // This must be an overloaded function that we handled special. + AutoDecRef special(Py_BuildValue("(OO)", ob_cf, PyName::overload())); + selftype = PyDict_GetItem(pyside_globals->map_dict, special); + if (selftype == nullptr) { + // This is probably a module function. We will return type(None). + selftype = Py_None; + } + } + } + + PyObject *obtype_mod = (PyType_Check(selftype) || PyModule_Check(selftype)) + ? selftype + : reinterpret_cast(Py_TYPE(selftype)); + Py_INCREF(obtype_mod); + return obtype_mod; +} + +PyObject *_get_class_of_sm(PyObject *ob_sm) +{ + AutoDecRef func(PyObject_GetAttr(ob_sm, PyMagicName::func())); + return _get_class_of_cf(func); +} + +PyObject *_get_class_of_descr(PyObject *ob) +{ + return PyObject_GetAttr(ob, PyMagicName::objclass()); +} + +PyObject *_address_to_stringlist(PyObject *numkey) +{ + /* + * This is a tiny optimization that saves initialization time. + * Instead of creating all Python strings during the call to + * `PySide_BuildSignatureArgs`, we store the address of the stringlist. + * When needed in `PySide_BuildSignatureProps`, the strings are + * finally materialized. + */ + ssize_t address = PyNumber_AsSsize_t(numkey, PyExc_ValueError); + if (address == -1 && PyErr_Occurred()) + return nullptr; + char **sig_strings = reinterpret_cast(address); + PyObject *res_list = PyList_New(0); + if (res_list == nullptr) + return nullptr; + for (; *sig_strings != nullptr; ++sig_strings) { + char *sig_str = *sig_strings; + AutoDecRef pystr(Py_BuildValue("s", sig_str)); + if (pystr.isNull() || PyList_Append(res_list, pystr) < 0) + return nullptr; + } + return res_list; +} + +static int _build_func_to_type(PyObject *obtype) +{ + /* + * There is no general way to directly get the type of a static method. + * On Python 3, the type is hidden in an unused pointer in the + * PyCFunction structure, but the Limited API does not allow to access + * this, either. + * + * In the end, it was easier to avoid such tricks and build an explicit + * mapping from function to type. + * + * We walk through the method list of the type + * and record the mapping from static method to this type in a dict. + * We also check for hidden methods, see below. + */ + auto *type = reinterpret_cast(obtype); + PyObject *dict = type->tp_dict; + PyMethodDef *meth = type->tp_methods; + + if (meth == nullptr) + return 0; + + for (; meth->ml_name != nullptr; meth++) { + /* + * It is possible that a method is overwritten by another + * attribute with the same name. This case was obviously provoked + * explicitly in "testbinding.TestObject.staticMethodDouble", + * where instead of the method a "PySide2.QtCore.Signal" object + * was in the dict. + * This overlap is also found in regular PySide under + * "PySide2.QtCore.QProcess.error" where again a signal object is + * returned. These hidden methods will be opened for the + * signature module by adding them under the name + * "{name}.overload". + */ + PyObject *descr = PyDict_GetItemString(dict, meth->ml_name); + PyObject *look_attr = meth->ml_flags & METH_STATIC ? PyMagicName::func() + : PyMagicName::name(); + int check_name = meth->ml_flags & METH_STATIC ? 0 : 1; + if (descr == nullptr) + return -1; + + // We first check all methods if one is hidden by something else. + AutoDecRef look(PyObject_GetAttr(descr, look_attr)); + AutoDecRef given(Py_BuildValue("s", meth->ml_name)); + if (look.isNull() + || (check_name && PyObject_RichCompareBool(look, given, Py_EQ) != 1)) { + PyErr_Clear(); + AutoDecRef cfunc(PyCFunction_NewEx( + meth, reinterpret_cast(type), nullptr)); + if (cfunc.isNull()) + return -1; + if (meth->ml_flags & METH_STATIC) + descr = PyStaticMethod_New(cfunc); + else + descr = PyDescr_NewMethod(type, meth); + if (descr == nullptr) + return -1; + char mangled_name[200]; + strcpy(mangled_name, meth->ml_name); + strcat(mangled_name, ".overload"); + if (PyDict_SetItemString(dict, mangled_name, descr) < 0) + return -1; + if (meth->ml_flags & METH_STATIC) { + // This is the special case where a static method is hidden. + AutoDecRef special(Py_BuildValue("(Os)", cfunc.object(), "overload")); + if (PyDict_SetItem(pyside_globals->map_dict, special, obtype) < 0) + return -1; + } + if (PyDict_SetItemString(pyside_globals->map_dict, mangled_name, obtype) < 0) + return -1; + continue; + } + // Then we insert the mapping for static methods. + if (meth->ml_flags & METH_STATIC) { + if (PyDict_SetItem(pyside_globals->map_dict, look, obtype) < 0) + return -1; + } + } + return 0; +} + +int _finish_nested_classes(PyObject *obdict) +{ + PyObject *key, *value, *obtype; + PyTypeObject *subtype; + Py_ssize_t pos = 0; + + if (obdict == nullptr) + return -1; + while (PyDict_Next(obdict, &pos, &key, &value)) { + if (PyType_Check(value)) { + obtype = value; + if (_build_func_to_type(obtype) < 0) + return -1; + // now continue with nested cases + subtype = reinterpret_cast(obtype); + if (_finish_nested_classes(subtype->tp_dict) < 0) + return -1; + } + } + return 0; +} + +} // extern "C" diff --git a/sources/shiboken2/libshiboken/signature/signature_p.h b/sources/shiboken2/libshiboken/signature/signature_p.h new file mode 100644 index 000000000..9444f3e9b --- /dev/null +++ b/sources/shiboken2/libshiboken/signature/signature_p.h @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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 SIGNATURE_IMPL_H +#define SIGNATURE_IMPL_H + +#include "signature.h" + +extern "C" { + +// signature_globals.cpp + +typedef struct safe_globals_struc { + // init part 1: get arg_dict + PyObject *helper_module; + PyObject *arg_dict; + PyObject *map_dict; + PyObject *value_dict; // for writing signatures + PyObject *feature_dict; // registry for PySide.support.__feature__ + // init part 2: run module + PyObject *pyside_type_init_func; + PyObject *create_signature_func; + PyObject *seterror_argument_func; + PyObject *make_helptext_func; + PyObject *finish_import_func; +} safe_globals_struc, *safe_globals; + +extern safe_globals pyside_globals; +extern PyMethodDef signature_methods[]; + +void init_module_1(void); +void init_module_2(void); + +// signature.cpp + +PyObject *GetTypeKey(PyObject *ob); + +PyObject *GetSignature_Function(PyObject *, PyObject *); +PyObject *GetSignature_TypeMod(PyObject *, PyObject *); +PyObject *GetSignature_Wrapper(PyObject *, PyObject *); + +PyObject *get_signature_intern(PyObject *ob, PyObject *modifier); +PyObject *PySide_BuildSignatureProps(PyObject *class_mod); +PyObject *GetClassOrModOf(PyObject *ob); + +// signature_extend.cpp + +PyObject *pyside_cf_get___signature__(PyObject *func, PyObject *modifier); +PyObject *pyside_sm_get___signature__(PyObject *sm, PyObject *modifier); +PyObject *pyside_md_get___signature__(PyObject *ob_md, PyObject *modifier); +PyObject *pyside_wd_get___signature__(PyObject *ob, PyObject *modifier); +PyObject *pyside_tp_get___signature__(PyObject *obtype_mod, PyObject *modifier); + +int PySide_PatchTypes(void); +PyObject *pyside_tp_get___doc__(PyObject *tp); + +// signature_helper.cpp + +PyObject *_get_qualname(PyObject *ob); +int add_more_getsets(PyTypeObject *type, PyGetSetDef *gsp, PyObject **doc_descr); +PyObject *name_key_to_func(PyObject *ob); +PyObject *_get_class_of_cf(PyObject *ob_cf); +PyObject *_get_class_of_sm(PyObject *ob_sm); +PyObject *_get_class_of_descr(PyObject *ob); +PyObject *_address_to_stringlist(PyObject *numkey); +int _finish_nested_classes(PyObject *dict); + +} // extern "C" + +#endif // SIGNATURE_IMPL_H diff --git a/sources/shiboken2/libshiboken/signature_doc.rst b/sources/shiboken2/libshiboken/signature_doc.rst deleted file mode 100644 index cb9041d0c..000000000 --- a/sources/shiboken2/libshiboken/signature_doc.rst +++ /dev/null @@ -1,357 +0,0 @@ -************************* -The signature C extension -************************* - -This module is a C extension for CPython 3.5 and up, and CPython 2.7. -Its purpose is to provide support for the ``__signature__`` attribute -of builtin PyCFunction objects. - - -Short Introduction to the Topic -=============================== - -Beginning with CPython 3.5, Python functions began to grow a ``__signature__`` -attribute for normal Python functions. This is totally optional and just -a nice-to-have feature in Python. - -PySide, on the other hand, could use ``__signature__`` very much, because the -typing info for the 15000+ PySide functions is really missing, and it -would be nice to have this info directly available. - - -The Idea to Support Signatures -============================== - -We want to have an additional ``__signature__`` attribute in all PySide -methods, without changing lots of generated code. -Therefore, we did not change any of the existing data structures, -but supported the new attribute by a global dictionary. - -When the ``__signature__`` property is requested, a method is called that -does a lookup in the global dict. This is a flexible approach with little impact -to the rest of the project. It has very limited overhead compared to direct -attribute access, but for the need of a signature access from time to time, -this is an adequate compromise. - - -How this Code Works -------------------- - -Signatures are supported for regular Python functions, only. Creating signatures -for ``PyCFunction`` objects would require quite some extra effort in Python. - -Fortunately, we found this special *stealth* technique, that saves us most of the -needed effort: - -The basic idea is to create a dummy Python function with **varnames**, **defaults** -and **annotations** properties, and then to use the inspect -module to create a signature object. This object is returned as the computed -result of the ``__signature__`` attribute of the real ``PyCFunction`` object. - -There is one thing that really changes Python a bit: - -* We added the ``__signature__`` attribute to every function. - -That is a little change to Python that does not harm, but it saves us -tons of code, that was needed in the early versions of the module. - -The internal work is done in two steps: - -* All functions of a class get the *signature text* when the module is imported. - This is only a very small overhead added to the startup time. It is a single - string for each whole class. -* The actual signature object is created later, when the attribute is really - requested. Signatures are cached and only created on first access. - -Example: - -The ``PyCFunction`` ``QtWidgets.QApplication.palette`` is interrogated for its -signature. That means ``pyside_sm_get___signature__()`` is called. -It calls ``GetSignature_Function`` which returns the signature if it is found. - - -Why this Code is Fast ---------------------- - -It costs a little time (maybe 6 seconds) to run througs every single signature -object, since these are more than 25000 Python objects. But all the signature -objects will be rarely accessed but in special applications. -The normal case are only a few accesses, and these are working pretty fast. - -The key to make this signature module fast is to avoid computation as much as -possible. When no signature objects are used, then almost no time is lost in -initialization. Only the above mentioned strings and some support modules are -additionally loaded on ``import PySide2``. -When it comes to signature usage, then late initialization is used and cached. -This technique is also known as *full laziness* in haskell. - -There are actually two locations where late initialization occurs: - -* ``dict`` can be no dict but a tuple. That is the initial argument tuple that - was saved by ``PySide_BuildSignatureArgs`` at module load time. - If so, then ``pyside_type_init`` in parser.py will be called, - which parses the string and creates the dict. -* ``props`` can be empty. Then ``create_signature`` in loader.py - is called, which uses a dummy function to produce a signature instance - with the inspect module. - -The initialization that is always done is just two dictionary writes -per class, and we have about 1000 classes. -To measure the additional overhead, we have simulated what happens -when ``from PySide2 import *`` is performed. -It turned out that the overhead is below 0.5 ms. - - -The Signature Package Structure -------------------------------- - -The C++ code involved with the signature module is completely in the file -shiboken2/libshiboken/signature.cpp . All other functionality is implemented in -the ``signature`` Python package. It has the following structure:: - - shiboken2/files.dir/shibokensupport/ - backport_inspect.py - - signature/ - loader.py - parser.py - mapping.py - errorhandler.py - layout.py - - lib/ - enum_sig.py - tool.py - - - -Really important are the **parser**, **mapping**, **errorhandler**, **enum_sig**, -**layout** and **loader** modules. The rest is needed to create Python 2 compatibility -or be compatible with embedding and installers. - - -loader.py -~~~~~~~~~ - -This module assembles and imports the ``inspect`` module, and then exports the -``create_signature`` function. This function takes a fake function and some -attributes and builds a ``__signature__`` object with the inspect module. - - -parser.py -~~~~~~~~~ - -This module takes a class signatures string from C++ and parses it into the -needed properties for the ``create_signature`` function. Its entry point is the -``pyside_type_init`` function, which is called from the C module via ``loader.py``. - - -mapping.py -~~~~~~~~~~ - -The purpose of the mapping module is maintaining a list of replacement strings -that map from the *signature text* in C to the property strings that Python -needs. A lot of mappings are resolved by rather complex expressions in ``parser.py``, -but a few hundred cases are better to spell explicitly, here. - - -errorhandler.py -~~~~~~~~~~~~~~~ - -Since ``Qt For Python 5.12``, we no longer use the builtin type error messages from C++. -Instead, we get much better results with the signature module. At the same time, -this enforced supporting shiboken as well, and the signature module was no longer -optional. - - -enum_sig.py -~~~~~~~~~~~ - -The diverse applications of the signature module all needed to iterate over modules, -classes and functions. In order to centralize this enumeration, the process has -been factored out as a context manager. The user has only to supply functions -that do the actual formatting. - -See for example the .pyi generator ``pyside2/PySide2/support/generate_pyi.py``. - - -layout.py -~~~~~~~~~ - -As more applications used the signature module, different formatting of signatures -was needed. To support that, we created the function ``create_signature``, which -has a parameter to choose from some prefefined layouts. - - -*typing27.py* -~~~~~~~~~~~~~ - -Python 2 has no typing module at all. This is a backport of the minimum that is needed. - - -*backport_inspect.py* -~~~~~~~~~~~~~~~~~~~~~ - -Python 2 has an inspect module, but lacks the signature functions, completely. -This module adds the missing functionality, which is merged at runtime into -the inspect module. - - -Multiple Arities ----------------- - -One aspect that was ignored so far was *multiple arities*: How to handle it when -a function has more than one signature? - -I did not find any note on how multiple signatures should be treated in Python, -but this simple rules seem to work well: - -* If there is a list, then it is a multi-signature. -* Otherwise, it is a simple signature. - - -Impacts of The Signature Module -=============================== - -The signature module has a number of impacts to other PySide modules, which were -created as a consequence of its existence, and there will be a few more in the -future: - - -existence_test.py ------------------ - -The file ``pyside2/tests/registry/existence_test.py`` was written using the -signatures from the signatures module. The idea is that there are some 15000 -functions with a certain signature. - -These functions should not get lost by some bad check-in. Therefore, a list -of all existing signatures is kept as a module that assembles a -dictionary. The function existence is checked, and also the exact arity. - -This module exists for every PySide release and every platform. The initial -module is generated once and saved as ``exists_{plat}_{version}.py``. - -An error is normally only reported as a warning, but: - - -Interaction With The Coin Module -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -When this test program is run in COIN, then the warnings are turned into -errors. The reason is that only in COIN, we have a stable configuration -of PySide modules that can reliably be compared. - -These modules have the name ``exists_{platf}_{version}_ci.py``, and as a big -exception for generated code, these files are *intentionally* checked in. - - -What Happens When a List is Missing? -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -When a new version of PySide gets created, then the existence test files -initially do not exist. - -When a COIN test is run, then it will complain about the error and create -the missing module on standard output. -But since COIN tests are run multiple times, the output that was generated -by the first test will still exist at the subsequent runs. -(If COIN was properly implemented, we could not take that advantage and -would need to implement that as an extra exception.) - -As a result, a missing module will be reported as a test which partially -succeeded (called "FLAKY"). To avoid further flaky tests and to activate as a real test, -we can now capture the error output of COIN and check the generated module -in. - - -Explicitly Enforcing Recreation -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The former way to regenerate the registry files was to remove the files -and check that in. This has the desired effect, but creates huge deltas. -As a more efficient way, we have prepared a comment in the first line -that contains the word "recreate". -By uncommenting this line, a NameError is triggered, which has the same -effect. - - -init_platform.py -~~~~~~~~~~~~~~~~ - -For generating the ``exists_{platf}_{version}`` modules, the module -``pyside2/tests/registry/init_platform.py`` was written. It can be used -standalone from the commandline, to check the compatibility of some -changes, directly. - - -scrape_testresults.py ---------------------- - -To simplify and automate the process of extracting the ``exists_{platf}_{version}_ci.py`` -files, the script ``pyside2/tests/registry/scrape_testresults.py`` has been written. - -This script scans the whole testresults website for PySide, that is:: - - https://testresults.qt.io/coin/api/results/pyside/pyside-setup/ - -On the first scan, the script runs less than 30 minutes. After that, a cache -is generated and the scan works *much* faster. The test results are placed -into the folder ``pyside2/tests/registry/testresults/embedded/`` with a -unique name that allows for easy sorting. Example:: - - testresults/embedded/2018_09_10_10_40_34-test_1536891759-exists_linux_5_11_2_ci.py - -These files are created only once. If they already exist, they are not touched, again. -The file `pyside2/tests/registry/known_urls.json`` holds all scanned URLs after -a successful scan. The ``testresults/embedded`` folder can be kept for reference -or can be removed. Important is only the json file. - -The result of a scan is then directly placed into the ``pyside2/tests/registry/`` -folder. It should be reviewed and then eventually checked in. - - -generate_pyi.py ---------------- - -``pyside2/PySide2/support/generate_pyi.py`` is still under development. -This module generates so-called hinting stubs for integration of PySide -with diverse *Python IDEs*. - -Although this module creates the stubs as an add-on, the -impact on the quality of the signature module is considerable: - -The module must create syntactically correct ``.pyi`` files which contain -not only signatures but also constants and enums of all PySide modules. -This serves as an extra challenge that has a very positive effect on -the completeness and correctness of signatures. - - -Current Extensions ------------------- - -Before the signature module was written, there already existed the concept of -signatures, but in a more C++ - centric way. From that time, there existed -the error messages, which are created when a function gets wrong argument types. - -These error messages were replaced by text generated on demand by -the signature module, in order to be more consistent and correct. -This was implemented in ``Qt For Python 5.12.0``. - -Additionally, the ``__doc__`` attribute of PySide methods was not set. -It was easy to get a nice ``help()`` feature by creating signatures -as default content for docstrings. -This was implemented in ``Qt For Python 5.12.1``. - - -Literature -========== - - `PEP 362 – Function Signature Object `__ - - `PEP 484 – Type Hints `__ - - `PEP 3107 – Function Annotations `__ - - -*Personal Remark: This module is dedicated to our lovebird "Püppi", who died on 2017-09-15.* -- cgit v1.2.3 From 5a7429cc895824a4eafd27e1a5c95b40bba86bdf Mon Sep 17 00:00:00 2001 From: Friedemann Kleint Date: Mon, 28 Sep 2020 08:12:38 +0200 Subject: Fix some sphinx warnings in Pyside2 documentation sources/pyside2/doc/deployment-briefcase.rst:163: WARNING: Title underline too short. sources/pyside2/doc/deployment-briefcase.rst:178: WARNING: Title underline too short. sources/pyside2/doc/deployment-briefcase.rst:178: WARNING: Title underline too short. sources/pyside2/doc/modules.rst:4: WARNING: toctree glob pattern 'PySide2/Qt*/index' didn't match any documents sources/pyside2/doc/tutorials/qmlsqlintegration/qmlsqlintegration.rst:103: WARNING: Unexpected indentation. sources/pyside2/doc/tutorials/qmlsqlintegration/qmlsqlintegration.rst:104: WARNING: Block quote ends without a blank line; unexpected unindent. sources/pyside2/doc/tutorials/qmlsqlintegration/qmlsqlintegration.rst:209: WARNING: Unexpected indentation. sources/pyside2/doc/tutorials/qmlsqlintegration/qmlsqlintegration.rst:211: WARNING: Block quote ends without a blank line; unexpected unindent. Change-Id: I06900906584644ed585a386803ff33939fd11103 Reviewed-by: Cristian Maureira-Fredes --- sources/pyside2/doc/deployment-briefcase.rst | 4 ++-- .../doc/tutorials/qmlsqlintegration/qmlsqlintegration.rst | 14 ++++++++------ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/sources/pyside2/doc/deployment-briefcase.rst b/sources/pyside2/doc/deployment-briefcase.rst index fec5e0d56..a5179a279 100644 --- a/sources/pyside2/doc/deployment-briefcase.rst +++ b/sources/pyside2/doc/deployment-briefcase.rst @@ -160,7 +160,7 @@ It creates a subdirectory each for the different platforms. This step takes longer as it adds the packages listed in `requires` sections in the `pyproject.toml` file. Build the application ---------------- +--------------------- :: briefcase build @@ -175,7 +175,7 @@ You'll get:: Run the application ------------ +------------------- :: briefcase run diff --git a/sources/pyside2/doc/tutorials/qmlsqlintegration/qmlsqlintegration.rst b/sources/pyside2/doc/tutorials/qmlsqlintegration/qmlsqlintegration.rst index c26d154ac..f675f7899 100644 --- a/sources/pyside2/doc/tutorials/qmlsqlintegration/qmlsqlintegration.rst +++ b/sources/pyside2/doc/tutorials/qmlsqlintegration/qmlsqlintegration.rst @@ -99,9 +99,10 @@ There are three properties that are almost always set when using ApplicationWind Once we've set these, we have a properly sized, empty window ready to be filled with content. There are two ways of laying out items in QML: `Item Positioners`_ and `Qt Quick Layouts`_. -* Item positioners (`Row`_, `Column`_, and so on) are useful for situations where the size of items + +- Item positioners (`Row`_, `Column`_, and so on) are useful for situations where the size of items is known or fixed, and all that is required is to neatly position them in a certain formation. -* The layouts in Qt Quick Layouts can both position and resize items, making them well suited for +- The layouts in Qt Quick Layouts can both position and resize items, making them well suited for resizable user interfaces. Below, we use `ColumnLayout`_ to vertically lay out a `ListView`_ and a `Pane`_. @@ -205,14 +206,15 @@ if it doesn't already exist. A few interesting things happen in the ``main`` function: -* Declaring a :ref:`QGuiApplication`. + +- Declaring a :ref:`QGuiApplication`. You should use a :ref:`QGuiApplication` instead of :ref:`QApplication` because we're not using the **QtWidgets** module. -* Connecting to the database, -* Declaring a :ref:`QQmlApplicationEngine`. +- Connecting to the database, +- Declaring a :ref:`QQmlApplicationEngine`. This allows you to access the QML context property to connect Python and QML from the conversation model we built on ``sqlDialog.py``. -* Loading the ``.qml`` file that defines the UI. +- Loading the ``.qml`` file that defines the UI. Finally, the Qt application runs, and your program starts. -- cgit v1.2.3 From cb073f6eee9e21b62d7c14e0357beb6cecf33e31 Mon Sep 17 00:00:00 2001 From: Friedemann Kleint Date: Thu, 24 Sep 2020 11:10:24 +0200 Subject: shiboken2: Generate properties as fields - Add an XML attribute specifying whether code is to be generated to the XML properties. - Split the generator functions for field setters and getters apart so that they can be used for generating property setters and getters. - Generate code for all properties from XML when the PySide extension is not used, otherwise only for those with the attribute set. Task-number: PYSIDE-1019 Change-Id: Iab2ba38b90038edc667a233c23c7113fdc6fb438 Reviewed-by: Christian Tismer Reviewed-by: Cristian Maureira-Fredes --- sources/shiboken2/ApiExtractor/abstractmetalang.h | 5 +- sources/shiboken2/ApiExtractor/propertyspec.cpp | 3 +- sources/shiboken2/ApiExtractor/propertyspec.h | 6 + sources/shiboken2/ApiExtractor/typesystem.h | 2 + .../shiboken2/ApiExtractor/typesystemparser.cpp | 5 + .../doc/typesystem_manipulating_objects.rst | 59 ++++++++-- .../shiboken2/generator/shiboken2/cppgenerator.cpp | 130 +++++++++++++++++---- .../shiboken2/generator/shiboken2/cppgenerator.h | 11 ++ .../generator/shiboken2/shibokengenerator.cpp | 29 ++++- .../generator/shiboken2/shibokengenerator.h | 20 +++- sources/shiboken2/tests/libsample/pen.cpp | 10 ++ sources/shiboken2/tests/libsample/pen.h | 5 + sources/shiboken2/tests/samplebinding/pen_test.py | 14 ++- .../tests/samplebinding/typesystem_sample.xml | 1 + 14 files changed, 258 insertions(+), 42 deletions(-) diff --git a/sources/shiboken2/ApiExtractor/abstractmetalang.h b/sources/shiboken2/ApiExtractor/abstractmetalang.h index 268546d6c..466464807 100644 --- a/sources/shiboken2/ApiExtractor/abstractmetalang.h +++ b/sources/shiboken2/ApiExtractor/abstractmetalang.h @@ -1592,10 +1592,7 @@ public: m_propertySpecs << spec; } - QVector propertySpecs() const - { - return m_propertySpecs; - } + const QVector &propertySpecs() const { return m_propertySpecs; } QPropertySpec *propertySpecByName(const QString &name) const; QPropertySpec *propertySpecForRead(const QString &name) const; diff --git a/sources/shiboken2/ApiExtractor/propertyspec.cpp b/sources/shiboken2/ApiExtractor/propertyspec.cpp index fcb8286bf..f86a31d5b 100644 --- a/sources/shiboken2/ApiExtractor/propertyspec.cpp +++ b/sources/shiboken2/ApiExtractor/propertyspec.cpp @@ -48,7 +48,8 @@ QPropertySpec::QPropertySpec(const TypeSystemProperty &ts, m_write(ts.write), m_designable(ts.designable), m_reset(ts.reset), - m_type(type) + m_type(type), + m_generateGetSetDef(ts.generateGetSetDef) { } diff --git a/sources/shiboken2/ApiExtractor/propertyspec.h b/sources/shiboken2/ApiExtractor/propertyspec.h index 4abe75c84..611d4726e 100644 --- a/sources/shiboken2/ApiExtractor/propertyspec.h +++ b/sources/shiboken2/ApiExtractor/propertyspec.h @@ -79,6 +79,7 @@ public: QString write() const { return m_write; } void setWrite(const QString &write) { m_write = write; } + bool hasWrite() const { return !m_write.isEmpty(); } QString designable() const { return m_designable; } void setDesignable(const QString &designable) { m_designable = designable; } @@ -89,6 +90,9 @@ public: int index() const { return m_index; } void setIndex(int index) {m_index = index; } + bool generateGetSetDef() const { return m_generateGetSetDef; } + void setGenerateGetSetDef(bool generateGetSetDef) { m_generateGetSetDef = generateGetSetDef; } + #ifndef QT_NO_DEBUG_STREAM void formatDebug(QDebug &d) const; #endif @@ -101,6 +105,8 @@ private: QString m_reset; const AbstractMetaType *m_type = nullptr; int m_index = -1; + // Indicates whether actual code is generated instead of relying on libpyside. + bool m_generateGetSetDef = false; }; #ifndef QT_NO_DEBUG_STREAM diff --git a/sources/shiboken2/ApiExtractor/typesystem.h b/sources/shiboken2/ApiExtractor/typesystem.h index 0c8f85738..8c4f1dc0e 100644 --- a/sources/shiboken2/ApiExtractor/typesystem.h +++ b/sources/shiboken2/ApiExtractor/typesystem.h @@ -563,6 +563,8 @@ struct TypeSystemProperty QString write; QString reset; QString designable; + // Indicates whether actual code is generated instead of relying on libpyside. + bool generateGetSetDef = false; }; class TypeEntry diff --git a/sources/shiboken2/ApiExtractor/typesystemparser.cpp b/sources/shiboken2/ApiExtractor/typesystemparser.cpp index 04aadbb63..27e613280 100644 --- a/sources/shiboken2/ApiExtractor/typesystemparser.cpp +++ b/sources/shiboken2/ApiExtractor/typesystemparser.cpp @@ -70,6 +70,7 @@ static inline QString formatAttribute() { return QStringLiteral("format"); } static inline QString generateUsingAttribute() { return QStringLiteral("generate-using"); } static inline QString classAttribute() { return QStringLiteral("class"); } static inline QString generateAttribute() { return QStringLiteral("generate"); } +static inline QString generateGetSetDefAttribute() { return QStringLiteral("generate-getsetdef"); } static inline QString genericClassAttribute() { return QStringLiteral("generic-class"); } static inline QString indexAttribute() { return QStringLiteral("index"); } static inline QString invalidateAfterUseAttribute() { return QStringLiteral("invalidate-after-use"); } @@ -2256,6 +2257,10 @@ bool TypeSystemParser::parseProperty(const QXmlStreamReader &, const StackElemen property.type = attributes->takeAt(i).value().toString(); } else if (name == QLatin1String("set")) { property.write = attributes->takeAt(i).value().toString(); + } else if (name == generateGetSetDefAttribute()) { + property.generateGetSetDef = + convertBoolean(attributes->takeAt(i).value(), + generateGetSetDefAttribute(), false); } } if (!property.isValid()) { diff --git a/sources/shiboken2/doc/typesystem_manipulating_objects.rst b/sources/shiboken2/doc/typesystem_manipulating_objects.rst index 98f503074..fb0a50aba 100644 --- a/sources/shiboken2/doc/typesystem_manipulating_objects.rst +++ b/sources/shiboken2/doc/typesystem_manipulating_objects.rst @@ -238,14 +238,23 @@ conversion-rule property ^^^^^^^^ - The ``property`` element allows you to add properties complementing the - properties obtained from the ``Q_PROPERTY`` macro in Qt-based code when using - the PySide2 extension. It may appear as a child of a complex type such as - ``object-type`` or ``value-type``. + The ``property`` element allows you to specify properties consisting of + a type and getter and setter functions. + + It may appear as a child of a complex type such as ``object-type`` or + ``value-type``. + + If the PySide2 extension is not present, code will be generated using the + ``PyGetSetDef`` struct, similar to what is generated for fields. + + If the PySide2 extension is present, those properties complement the + properties obtained from the ``Q_PROPERTY`` macro in Qt-based code. + The properties will be handled in ``libpyside`` unless code generation + is forced. .. code-block:: xml - + The ``name`` attribute specifies the name of the property, the ``type`` attribute specifies the C++ type and the ``get`` attribute specifies the @@ -253,15 +262,49 @@ property The optional ``set`` attribute specifies name of the setter function. + The optional ``generate-getsetdef`` attribute specifies whether to generate + code for if the PySide2 extension is present (indicating this property is not + handled by libpyside). It defaults to *no*. + The optional ``since`` attribute specifies the API version when this property appears. - For example: + For a typical C++ class, like: + + .. code-block:: c++ + + class Test { + public: + int getValue() const; + void setValue(); + }; + + ``value`` can then be specified to be a property: + + .. code-block:: xml + + + + + With that, a more pythonic style can be used: + + .. code-block:: python + + test = Test() + test.value = 42 + + For Qt classes (with the PySide2 extension present), additional setters + and getters that do not appear as ``Q_PROPERTY``, can be specified to + be properties: .. code-block:: xml - specifies ``centralWidget`` to be a Python property in addition to the normal properties - of ``QMainWindow`` defined for Qt Designer usage. + in addition to the normal properties of ``QMainWindow`` defined for + Qt Designer usage. + + .. note:: In the *Qt* coding style, the property name typically conflicts + with the getter name. It is recommended to exclude the getter from the + wrapper generation using the ``remove`` function modification. diff --git a/sources/shiboken2/generator/shiboken2/cppgenerator.cpp b/sources/shiboken2/generator/shiboken2/cppgenerator.cpp index 32b9cf24f..6d9884d39 100644 --- a/sources/shiboken2/generator/shiboken2/cppgenerator.cpp +++ b/sources/shiboken2/generator/shiboken2/cppgenerator.cpp @@ -317,6 +317,13 @@ static QString buildPropertyString(QPropertySpec *spec) return text; } +static void writePyGetSetDefEntry(QTextStream &s, const QString &name, + const QString &getFunc, const QString &setFunc) +{ + s << "{const_cast(\"" << name << "\"), " << getFunc << ", " + << (setFunc.isEmpty() ? QLatin1String(NULL_PTR) : setFunc) << "},\n"; +} + /*! Function used to write the class generated binding code on the buffer \param s the output buffer @@ -590,8 +597,10 @@ void CppGenerator::generateClass(QTextStream &s, const GeneratorContext &classCo // PYSIDE-1019: Write a compressed list of all properties `name:getter[:setter]`. // Default values are suppressed. QStringList sorter; - for (const auto spec : metaClass->propertySpecs()) - sorter.append(buildPropertyString(spec)); + for (const auto spec : metaClass->propertySpecs()) { + if (!spec->generateGetSetDef()) + sorter.append(buildPropertyString(spec)); + } sorter.sort(); s << '\n'; @@ -689,19 +698,34 @@ void CppGenerator::generateClass(QTextStream &s, const GeneratorContext &classCo s << Qt::endl; } + for (const QPropertySpec *property : metaClass->propertySpecs()) { + if (property->generateGetSetDef() || !usePySideExtensions()) { + writeGetterFunction(s, property, classContext); + if (property->hasWrite()) + writeSetterFunction(s, property, classContext); + } + } + s << "// Getters and Setters for " << metaClass->name() << Qt::endl; s << "static PyGetSetDef " << cpythonGettersSettersDefinitionName(metaClass) << "[] = {\n"; for (const AbstractMetaField *metaField : fields) { - if (metaField->isStatic()) - continue; + if (!metaField->isStatic()) { + s << INDENT; + const QString setter = canGenerateFieldSetter(metaField) + ? cpythonSetterFunctionName(metaField) : QString(); + writePyGetSetDefEntry(s, metaField->name(), + cpythonGetterFunctionName(metaField), setter); + } + } - s << INDENT << "{const_cast(\"" << metaField->name() << "\"), "; - s << cpythonGetterFunctionName(metaField) << ", "; - if (canGenerateFieldSetter(metaField)) - s << cpythonSetterFunctionName(metaField); - else - s << NULL_PTR; - s << "},\n"; + for (const QPropertySpec *property : metaClass->propertySpecs()) { + if (property->generateGetSetDef() || !usePySideExtensions()) { + s << INDENT; + const QString setter = property->hasWrite() + ? cpythonSetterFunctionName(property, metaClass) : QString(); + writePyGetSetDefEntry(s, property->name(), + cpythonGetterFunctionName(property, metaClass), setter); + } } s << INDENT << '{' << NULL_PTR << "} // Sentinel\n"; s << "};\n\n"; @@ -3976,6 +4000,13 @@ bool CppGenerator::shouldGenerateGetSetList(const AbstractMetaClass *metaClass) if (!f->isStatic()) return true; } + // Generate all user-added properties unless Pyside extensions are used, + // in which only the explicitly specified ones are generated (rest is handled + // in libpyside). + return usePySideExtensions() + ? std::any_of(metaClass->propertySpecs().cbegin(), metaClass->propertySpecs().cend(), + [] (const QPropertySpec *s) { return s->generateGetSetDef(); }) + : !metaClass->propertySpecs().isEmpty(); return false; } @@ -4416,13 +4447,18 @@ void CppGenerator::writeCopyFunction(QTextStream &s, const GeneratorContext &con s << Qt::endl; } +static inline void writeGetterFunctionStart(QTextStream &s, const QString &funcName) +{ + s << "static PyObject *" << funcName << "(PyObject *self, void *)\n"; + s << "{\n"; +} + void CppGenerator::writeGetterFunction(QTextStream &s, const AbstractMetaField *metaField, const GeneratorContext &context) { ErrorCode errorCode(QString::fromLatin1(NULL_PTR)); - s << "static PyObject *" << cpythonGetterFunctionName(metaField) << "(PyObject *self, void *)\n"; - s << "{\n"; + writeGetterFunctionStart(s, cpythonGetterFunctionName(metaField)); writeCppSelfDefinition(s, context); @@ -4503,12 +4539,35 @@ void CppGenerator::writeGetterFunction(QTextStream &s, s << "}\n"; } -void CppGenerator::writeSetterFunction(QTextStream &s, - const AbstractMetaField *metaField, +// Write a getter for QPropertySpec +void CppGenerator::writeGetterFunction(QTextStream &s, const QPropertySpec *property, const GeneratorContext &context) { ErrorCode errorCode(0); - s << "static int " << cpythonSetterFunctionName(metaField) << "(PyObject *self, PyObject *pyIn, void *)\n"; + writeGetterFunctionStart(s, cpythonGetterFunctionName(property, context.metaClass())); + writeCppSelfDefinition(s, context); + const QString value = QStringLiteral("value"); + s << INDENT << "auto " << value << " = " << CPP_SELF_VAR << "->" << property->read() << "();\n" + << INDENT << "auto pyResult = "; + writeToPythonConversion(s, property->type(), context.metaClass(), value); + s << ";\n" + << INDENT << "if (PyErr_Occurred() || !pyResult) {\n"; + { + Indentation indent(INDENT); + s << INDENT << "Py_XDECREF(pyResult);\n" + << INDENT << " return {};\n"; + } + s << INDENT << "}\n" + << INDENT << "return pyResult;\n}\n\n"; +} + +// Write setter function preamble (type checks on "pyIn") +void CppGenerator::writeSetterFunctionPreamble(QTextStream &s, const QString &name, + const QString &funcName, + const AbstractMetaType *type, + const GeneratorContext &context) +{ + s << "static int " << funcName << "(PyObject *self, PyObject *pyIn, void *)\n"; s << "{\n"; writeCppSelfDefinition(s, context); @@ -4517,24 +4576,33 @@ void CppGenerator::writeSetterFunction(QTextStream &s, { Indentation indent(INDENT); s << INDENT << "PyErr_SetString(PyExc_TypeError, \"'"; - s << metaField->name() << "' may not be deleted\");\n"; + s << name << "' may not be deleted\");\n"; s << INDENT << "return -1;\n"; } s << INDENT << "}\n"; - AbstractMetaType *fieldType = metaField->type(); - s << INDENT << "PythonToCppFunc " << PYTHON_TO_CPP_VAR << "{nullptr};\n"; s << INDENT << "if (!"; - writeTypeCheck(s, fieldType, QLatin1String("pyIn"), isNumber(fieldType->typeEntry())); + writeTypeCheck(s, type, QLatin1String("pyIn"), isNumber(type->typeEntry())); s << ") {\n"; { Indentation indent(INDENT); s << INDENT << "PyErr_SetString(PyExc_TypeError, \"wrong type attributed to '"; - s << metaField->name() << "', '" << fieldType->name() << "' or convertible type expected\");\n"; + s << name << "', '" << type->name() << "' or convertible type expected\");\n"; s << INDENT << "return -1;\n"; } s << INDENT<< "}\n\n"; +} + +void CppGenerator::writeSetterFunction(QTextStream &s, + const AbstractMetaField *metaField, + const GeneratorContext &context) +{ + ErrorCode errorCode(0); + + AbstractMetaType *fieldType = metaField->type(); + writeSetterFunctionPreamble(s, metaField->name(), cpythonSetterFunctionName(metaField), + fieldType, context); QString cppField = QString::fromLatin1("%1->%2").arg(QLatin1String(CPP_SELF_VAR), metaField->name()); s << INDENT; @@ -4568,6 +4636,26 @@ void CppGenerator::writeSetterFunction(QTextStream &s, s << "}\n"; } +// Write a setter for QPropertySpec +void CppGenerator::writeSetterFunction(QTextStream &s, const QPropertySpec *property, + const GeneratorContext &context) +{ + ErrorCode errorCode(0); + writeSetterFunctionPreamble(s, property->name(), + cpythonSetterFunctionName(property, context.metaClass()), + property->type(), context); + + s << INDENT << "auto cppOut = " << CPP_SELF_VAR << "->" << property->read() << "();\n" + << INDENT << PYTHON_TO_CPP_VAR << "(pyIn, &cppOut);\n" + << INDENT << "if (PyErr_Occurred())\n"; + { + Indentation indent(INDENT); + s << INDENT << "return -1;\n"; + } + s << INDENT << CPP_SELF_VAR << "->" << property->write() << "(cppOut);\n" + << INDENT << "return 0;\n}\n\n"; +} + void CppGenerator::writeRichCompareFunction(QTextStream &s, const GeneratorContext &context) { const AbstractMetaClass *metaClass = context.metaClass(); diff --git a/sources/shiboken2/generator/shiboken2/cppgenerator.h b/sources/shiboken2/generator/shiboken2/cppgenerator.h index 1cc6c1ca0..8c56dd758 100644 --- a/sources/shiboken2/generator/shiboken2/cppgenerator.h +++ b/sources/shiboken2/generator/shiboken2/cppgenerator.h @@ -290,9 +290,20 @@ private: void writeGetterFunction(QTextStream &s, const AbstractMetaField *metaField, const GeneratorContext &context); + void writeGetterFunction(QTextStream &s, + const QPropertySpec *property, + const GeneratorContext &context); + void writeSetterFunctionPreamble(QTextStream &s, + const QString &name, + const QString &funcName, + const AbstractMetaType *type, + const GeneratorContext &context); void writeSetterFunction(QTextStream &s, const AbstractMetaField *metaField, const GeneratorContext &context); + void writeSetterFunction(QTextStream &s, + const QPropertySpec *property, + const GeneratorContext &context); void writeRichCompareFunction(QTextStream &s, const GeneratorContext &context); diff --git a/sources/shiboken2/generator/shiboken2/shibokengenerator.cpp b/sources/shiboken2/generator/shiboken2/shibokengenerator.cpp index ff1478d58..df8bf392a 100644 --- a/sources/shiboken2/generator/shiboken2/shibokengenerator.cpp +++ b/sources/shiboken2/generator/shiboken2/shibokengenerator.cpp @@ -31,6 +31,7 @@ #include #include #include "overloaddata.h" +#include "propertyspec.h" #include #include #include @@ -452,14 +453,38 @@ QString ShibokenGenerator::cpythonGetattroFunctionName(const AbstractMetaClass * return cpythonBaseName(metaClass) + QLatin1String("_getattro"); } +QString ShibokenGenerator::cpythonGetterFunctionName(const QString &name, + const AbstractMetaClass *enclosingClass) +{ + return cpythonBaseName(enclosingClass) + QStringLiteral("_get_") + name; +} + +QString ShibokenGenerator::cpythonSetterFunctionName(const QString &name, + const AbstractMetaClass *enclosingClass) +{ + return cpythonBaseName(enclosingClass) + QStringLiteral("_set_") + name; +} + QString ShibokenGenerator::cpythonGetterFunctionName(const AbstractMetaField *metaField) { - return QStringLiteral("%1_get_%2").arg(cpythonBaseName(metaField->enclosingClass()), metaField->name()); + return cpythonGetterFunctionName(metaField->name(), metaField->enclosingClass()); } QString ShibokenGenerator::cpythonSetterFunctionName(const AbstractMetaField *metaField) { - return QStringLiteral("%1_set_%2").arg(cpythonBaseName(metaField->enclosingClass()), metaField->name()); + return cpythonSetterFunctionName(metaField->name(), metaField->enclosingClass()); +} + +QString ShibokenGenerator::cpythonGetterFunctionName(const QPropertySpec *property, + const AbstractMetaClass *metaClass) +{ + return cpythonGetterFunctionName(property->name(), metaClass); +} + +QString ShibokenGenerator::cpythonSetterFunctionName(const QPropertySpec *property, + const AbstractMetaClass *metaClass) +{ + return cpythonSetterFunctionName(property->name(), metaClass); } static QString cpythonEnumFlagsName(const QString &moduleName, diff --git a/sources/shiboken2/generator/shiboken2/shibokengenerator.h b/sources/shiboken2/generator/shiboken2/shibokengenerator.h index da0c16851..83e7cf5b9 100644 --- a/sources/shiboken2/generator/shiboken2/shibokengenerator.h +++ b/sources/shiboken2/generator/shiboken2/shibokengenerator.h @@ -53,6 +53,7 @@ extern const char *END_ALLOW_THREADS; class DocParser; class CodeSnip; +class QPropertySpec; class OverloadData; struct GeneratorClassInfoCacheEntry; @@ -308,9 +309,9 @@ protected: static QString cpythonBaseName(const AbstractMetaClass *metaClass); static QString cpythonBaseName(const TypeEntry *type); - QString cpythonBaseName(const AbstractMetaType *type); - QString cpythonTypeName(const AbstractMetaClass *metaClass); - QString cpythonTypeName(const TypeEntry *type); + static QString cpythonBaseName(const AbstractMetaType *type); + static QString cpythonTypeName(const AbstractMetaClass *metaClass); + static QString cpythonTypeName(const TypeEntry *type); QString cpythonTypeNameExt(const TypeEntry *type) const; QString cpythonTypeNameExt(const AbstractMetaType *type) const; QString cpythonCheckFunction(const TypeEntry *type, bool genericNumberType = false); @@ -341,8 +342,12 @@ protected: QString cpythonGettersSettersDefinitionName(const AbstractMetaClass *metaClass); static QString cpythonGetattroFunctionName(const AbstractMetaClass *metaClass); static QString cpythonSetattroFunctionName(const AbstractMetaClass *metaClass); - QString cpythonGetterFunctionName(const AbstractMetaField *metaField); - QString cpythonSetterFunctionName(const AbstractMetaField *metaField); + static QString cpythonGetterFunctionName(const AbstractMetaField *metaField); + static QString cpythonSetterFunctionName(const AbstractMetaField *metaField); + static QString cpythonGetterFunctionName(const QPropertySpec *property, + const AbstractMetaClass *metaClass); + static QString cpythonSetterFunctionName(const QPropertySpec *property, + const AbstractMetaClass *metaClass); QString cpythonWrapperCPtr(const AbstractMetaClass *metaClass, const QString &argName = QLatin1String("self")) const; QString cpythonWrapperCPtr(const AbstractMetaType *metaType, const QString &argName) const; @@ -461,6 +466,11 @@ protected: static QStringList m_knownPythonTypes; private: + static QString cpythonGetterFunctionName(const QString &name, + const AbstractMetaClass *enclosingClass); + static QString cpythonSetterFunctionName(const QString &name, + const AbstractMetaClass *enclosingClass); + static const GeneratorClassInfoCacheEntry &getGeneratorClassInfo(const AbstractMetaClass *scope); static FunctionGroups getFunctionGroupsImpl(const AbstractMetaClass *scope); static bool classNeedsGetattroFunctionImpl(const AbstractMetaClass *metaClass); diff --git a/sources/shiboken2/tests/libsample/pen.cpp b/sources/shiboken2/tests/libsample/pen.cpp index b08721f79..1f39e7cbb 100644 --- a/sources/shiboken2/tests/libsample/pen.cpp +++ b/sources/shiboken2/tests/libsample/pen.cpp @@ -69,3 +69,13 @@ int Pen::ctorType() void Pen::drawLine(int x1, int y1, int x2, int y2, RenderHints renderHints) { } + +Pen::RenderHints Pen::getRenderHints() const +{ + return m_renderHints; +} + +void Pen::setRenderHints(RenderHints h) +{ + m_renderHints = h; +} diff --git a/sources/shiboken2/tests/libsample/pen.h b/sources/shiboken2/tests/libsample/pen.h index 6b3bf9f1a..20977fdeb 100644 --- a/sources/shiboken2/tests/libsample/pen.h +++ b/sources/shiboken2/tests/libsample/pen.h @@ -60,8 +60,13 @@ public: void drawLine(int x1, int y1, int x2, int y2, RenderHints renderHints = {}); int ctorType(); + + RenderHints getRenderHints() const; + void setRenderHints(RenderHints h); + private: int m_ctor; + RenderHints m_renderHints = None; }; #endif diff --git a/sources/shiboken2/tests/samplebinding/pen_test.py b/sources/shiboken2/tests/samplebinding/pen_test.py index 89abf4d54..ff6ee5e0e 100644 --- a/sources/shiboken2/tests/samplebinding/pen_test.py +++ b/sources/shiboken2/tests/samplebinding/pen_test.py @@ -65,7 +65,19 @@ class TestPen(unittest.TestCase): self.assertEqual(pen.ctorType(), Pen.ColorCtor) pen.drawLine(0, 0, 5, 5) + def testPenRenderHintsProperty(self): + """Exercise the generated property setter and getters, checking + against the C++ getter/setter functions.""" + pen = Pen(1) + self.assertEqual(pen.getRenderHints(), Pen.RenderHints.None_) + self.assertEqual(pen.renderHints, Pen.RenderHints.None_) + pen.renderHints = Pen.RenderHints.TextAntialiasing + self.assertEqual(pen.getRenderHints(), Pen.RenderHints.TextAntialiasing) + self.assertEqual(pen.renderHints, Pen.RenderHints.TextAntialiasing) + pen.setRenderHints(Pen.RenderHints.Antialiasing) + self.assertEqual(pen.getRenderHints(), Pen.RenderHints.Antialiasing) + self.assertEqual(pen.renderHints, Pen.RenderHints.Antialiasing) + if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken2/tests/samplebinding/typesystem_sample.xml b/sources/shiboken2/tests/samplebinding/typesystem_sample.xml index 132bff4ed..73f8542fc 100644 --- a/sources/shiboken2/tests/samplebinding/typesystem_sample.xml +++ b/sources/shiboken2/tests/samplebinding/typesystem_sample.xml @@ -2342,6 +2342,7 @@ + -- cgit v1.2.3 From dcddf43d39edaf992821767e4e3a53ebbfa15292 Mon Sep 17 00:00:00 2001 From: Friedemann Kleint Date: Mon, 28 Sep 2020 15:56:32 +0200 Subject: shiboken2: MetaBuilder/ApiExtractor/Generator: Return class/enum/function lists by const ref This allows for removing a few temporary variables and/or qAsConst(). Task-number: PYSIDE-1075 Change-Id: I0fe3544f0ce4d3a8d56de9c93e300972e0844177 Reviewed-by: Christian Tismer --- sources/shiboken2/ApiExtractor/abstractmetabuilder.cpp | 10 +++++----- sources/shiboken2/ApiExtractor/abstractmetabuilder.h | 10 +++++----- sources/shiboken2/ApiExtractor/apiextractor.cpp | 8 ++++---- sources/shiboken2/ApiExtractor/apiextractor.h | 8 ++++---- sources/shiboken2/generator/generator.cpp | 12 +++++------- sources/shiboken2/generator/generator.h | 6 +++--- sources/shiboken2/generator/qtdoc/qtdocgenerator.cpp | 6 ++---- sources/shiboken2/generator/shiboken2/cppgenerator.cpp | 3 +-- sources/shiboken2/generator/shiboken2/shibokengenerator.cpp | 6 ++---- 9 files changed, 31 insertions(+), 38 deletions(-) diff --git a/sources/shiboken2/ApiExtractor/abstractmetabuilder.cpp b/sources/shiboken2/ApiExtractor/abstractmetabuilder.cpp index 4197c4cfa..b46ed5092 100644 --- a/sources/shiboken2/ApiExtractor/abstractmetabuilder.cpp +++ b/sources/shiboken2/ApiExtractor/abstractmetabuilder.cpp @@ -135,27 +135,27 @@ AbstractMetaBuilder::~AbstractMetaBuilder() delete d; } -AbstractMetaClassList AbstractMetaBuilder::classes() const +const AbstractMetaClassList &AbstractMetaBuilder::classes() const { return d->m_metaClasses; } -AbstractMetaClassList AbstractMetaBuilder::templates() const +const AbstractMetaClassList &AbstractMetaBuilder::templates() const { return d->m_templates; } -AbstractMetaClassList AbstractMetaBuilder::smartPointers() const +const AbstractMetaClassList &AbstractMetaBuilder::smartPointers() const { return d->m_smartPointers; } -AbstractMetaFunctionList AbstractMetaBuilder::globalFunctions() const +const AbstractMetaFunctionList &AbstractMetaBuilder::globalFunctions() const { return d->m_globalFunctions; } -AbstractMetaEnumList AbstractMetaBuilder::globalEnums() const +const AbstractMetaEnumList &AbstractMetaBuilder::globalEnums() const { return d->m_globalEnums; } diff --git a/sources/shiboken2/ApiExtractor/abstractmetabuilder.h b/sources/shiboken2/ApiExtractor/abstractmetabuilder.h index 37022a544..d2dc080a2 100644 --- a/sources/shiboken2/ApiExtractor/abstractmetabuilder.h +++ b/sources/shiboken2/ApiExtractor/abstractmetabuilder.h @@ -63,11 +63,11 @@ public: AbstractMetaBuilder(); virtual ~AbstractMetaBuilder(); - AbstractMetaClassList classes() const; - AbstractMetaClassList templates() const; - AbstractMetaClassList smartPointers() const; - AbstractMetaFunctionList globalFunctions() const; - AbstractMetaEnumList globalEnums() const; + const AbstractMetaClassList &classes() const; + const AbstractMetaClassList &templates() const; + const AbstractMetaClassList &smartPointers() const; + const AbstractMetaFunctionList &globalFunctions() const; + const AbstractMetaEnumList &globalEnums() const; AbstractMetaEnum *findEnum(const TypeEntry *typeEntry) const; /** diff --git a/sources/shiboken2/ApiExtractor/apiextractor.cpp b/sources/shiboken2/ApiExtractor/apiextractor.cpp index 530ed0252..3fdd613ae 100644 --- a/sources/shiboken2/ApiExtractor/apiextractor.cpp +++ b/sources/shiboken2/ApiExtractor/apiextractor.cpp @@ -121,25 +121,25 @@ void ApiExtractor::setDropTypeEntries(QString dropEntries) TypeDatabase::instance()->setDropTypeEntries(entries); } -AbstractMetaEnumList ApiExtractor::globalEnums() const +const AbstractMetaEnumList &ApiExtractor::globalEnums() const { Q_ASSERT(m_builder); return m_builder->globalEnums(); } -AbstractMetaFunctionList ApiExtractor::globalFunctions() const +const AbstractMetaFunctionList &ApiExtractor::globalFunctions() const { Q_ASSERT(m_builder); return m_builder->globalFunctions(); } -AbstractMetaClassList ApiExtractor::classes() const +const AbstractMetaClassList &ApiExtractor::classes() const { Q_ASSERT(m_builder); return m_builder->classes(); } -AbstractMetaClassList ApiExtractor::smartPointers() const +const AbstractMetaClassList &ApiExtractor::smartPointers() const { Q_ASSERT(m_builder); return m_builder->smartPointers(); diff --git a/sources/shiboken2/ApiExtractor/apiextractor.h b/sources/shiboken2/ApiExtractor/apiextractor.h index f6dd2ba8e..b0c2c696f 100644 --- a/sources/shiboken2/ApiExtractor/apiextractor.h +++ b/sources/shiboken2/ApiExtractor/apiextractor.h @@ -81,10 +81,10 @@ public: LanguageLevel languageLevel() const; void setLanguageLevel(LanguageLevel languageLevel); - AbstractMetaEnumList globalEnums() const; - AbstractMetaFunctionList globalFunctions() const; - AbstractMetaClassList classes() const; - AbstractMetaClassList smartPointers() const; + const AbstractMetaEnumList &globalEnums() const; + const AbstractMetaFunctionList &globalFunctions() const; + const AbstractMetaClassList &classes() const; + const AbstractMetaClassList &smartPointers() const; AbstractMetaClassList classesTopologicalSorted(const Dependencies &additionalDependencies = Dependencies()) const; PrimitiveTypeEntryList primitiveTypes() const; ContainerTypeEntryList containerTypes() const; diff --git a/sources/shiboken2/generator/generator.cpp b/sources/shiboken2/generator/generator.cpp index ccece49e0..926840235 100644 --- a/sources/shiboken2/generator/generator.cpp +++ b/sources/shiboken2/generator/generator.cpp @@ -313,11 +313,9 @@ void Generator::collectInstantiatedContainersAndSmartPointers(const AbstractMeta void Generator::collectInstantiatedContainersAndSmartPointers() { - const AbstractMetaFunctionList &funcs = globalFunctions(); - for (const AbstractMetaFunction *func : funcs) + for (const AbstractMetaFunction *func : globalFunctions()) collectInstantiatedContainersAndSmartPointers(func); - const AbstractMetaClassList &classList = classes(); - for (const AbstractMetaClass *metaClass : classList) + for (const AbstractMetaClass *metaClass : classes()) collectInstantiatedContainersAndSmartPointers(metaClass); } @@ -341,7 +339,7 @@ bool Generator::handleOption(const QString & /* key */, const QString & /* value return false; } -AbstractMetaClassList Generator::classes() const +const AbstractMetaClassList &Generator::classes() const { return m_d->apiextractor->classes(); } @@ -351,12 +349,12 @@ AbstractMetaClassList Generator::classesTopologicalSorted(const Dependencies &ad return m_d->apiextractor->classesTopologicalSorted(additionalDependencies); } -AbstractMetaFunctionList Generator::globalFunctions() const +const AbstractMetaFunctionList &Generator::globalFunctions() const { return m_d->apiextractor->globalFunctions(); } -AbstractMetaEnumList Generator::globalEnums() const +const AbstractMetaEnumList &Generator::globalEnums() const { return m_d->apiextractor->globalEnums(); } diff --git a/sources/shiboken2/generator/generator.h b/sources/shiboken2/generator/generator.h index 5a55422a1..324bb8474 100644 --- a/sources/shiboken2/generator/generator.h +++ b/sources/shiboken2/generator/generator.h @@ -216,7 +216,7 @@ public: virtual bool handleOption(const QString &key, const QString &value); /// Returns the classes used to generate the binding code. - AbstractMetaClassList classes() const; + const AbstractMetaClassList &classes() const; /// Returns the output directory QString outputDirectory() const; @@ -285,10 +285,10 @@ protected: AbstractMetaClassList classesTopologicalSorted(const Dependencies &additionalDependencies = Dependencies()) const; /// Returns all global functions found by APIExtractor - AbstractMetaFunctionList globalFunctions() const; + const AbstractMetaFunctionList &globalFunctions() const; /// Returns all global enums found by APIExtractor - AbstractMetaEnumList globalEnums() const; + const AbstractMetaEnumList &globalEnums() const; /// Returns all primitive types found by APIExtractor PrimitiveTypeEntryList primitiveTypes() const; diff --git a/sources/shiboken2/generator/qtdoc/qtdocgenerator.cpp b/sources/shiboken2/generator/qtdoc/qtdocgenerator.cpp index 3b9dbf85e..6a01bf7a0 100644 --- a/sources/shiboken2/generator/qtdoc/qtdocgenerator.cpp +++ b/sources/shiboken2/generator/qtdoc/qtdocgenerator.cpp @@ -411,8 +411,7 @@ QString QtXmlToSphinx::expandFunction(const QString& function) const const AbstractMetaClass *metaClass = nullptr; if (firstDot != -1) { const QStringRef className = function.leftRef(firstDot); - const AbstractMetaClassList &classes = m_generator->classes(); - for (const AbstractMetaClass *cls : classes) { + for (const AbstractMetaClass *cls : m_generator->classes()) { if (cls->name() == className) { metaClass = cls; break; @@ -431,8 +430,7 @@ QString QtXmlToSphinx::resolveContextForMethod(const QString& methodName) const const QStringRef currentClass = m_context.splitRef(QLatin1Char('.')).constLast(); const AbstractMetaClass *metaClass = nullptr; - const AbstractMetaClassList &classes = m_generator->classes(); - for (const AbstractMetaClass *cls : classes) { + for (const AbstractMetaClass *cls : m_generator->classes()) { if (cls->name() == currentClass) { metaClass = cls; break; diff --git a/sources/shiboken2/generator/shiboken2/cppgenerator.cpp b/sources/shiboken2/generator/shiboken2/cppgenerator.cpp index 6d9884d39..af7c9777a 100644 --- a/sources/shiboken2/generator/shiboken2/cppgenerator.cpp +++ b/sources/shiboken2/generator/shiboken2/cppgenerator.cpp @@ -5869,8 +5869,7 @@ bool CppGenerator::finishGeneration() // Global enums AbstractMetaEnumList globalEnums = this->globalEnums(); - const AbstractMetaClassList &classList = classes(); - for (const AbstractMetaClass *metaClass : classList) { + for (const AbstractMetaClass *metaClass : classes()) { const AbstractMetaClass *encClass = metaClass->enclosingClass(); if (!encClass || !NamespaceTypeEntry::isVisibleScope(encClass->typeEntry())) lookForEnumsInClassesNotToBeGenerated(globalEnums, metaClass); diff --git a/sources/shiboken2/generator/shiboken2/shibokengenerator.cpp b/sources/shiboken2/generator/shiboken2/shibokengenerator.cpp index df8bf392a..97cbd26e9 100644 --- a/sources/shiboken2/generator/shiboken2/shibokengenerator.cpp +++ b/sources/shiboken2/generator/shiboken2/shibokengenerator.cpp @@ -1615,8 +1615,7 @@ AbstractMetaFunctionList ShibokenGenerator::filterFunctions(const AbstractMetaCl ShibokenGenerator::ExtendedConverterData ShibokenGenerator::getExtendedConverters() const { ExtendedConverterData extConvs; - const AbstractMetaClassList &classList = classes(); - for (const AbstractMetaClass *metaClass : classList) { + for (const AbstractMetaClass *metaClass : classes()) { // Use only the classes for the current module. if (!shouldGenerate(metaClass)) continue; @@ -2647,8 +2646,7 @@ bool ShibokenGenerator::doSetup() const ContainerTypeEntryList &containerTypeList = containerTypes(); for (const ContainerTypeEntry *type : containerTypeList) getCode(snips, type); - const AbstractMetaClassList &classList = classes(); - for (const AbstractMetaClass *metaClass : classList) + for (const AbstractMetaClass *metaClass : classes()) getCode(snips, metaClass->typeEntry()); const TypeSystemTypeEntry *moduleEntry = TypeDatabase::instance()->defaultTypeSystemType(); -- cgit v1.2.3 From 4b091251714aa16de912ef8fd9fed0f452ddf1f8 Mon Sep 17 00:00:00 2001 From: Friedemann Kleint Date: Mon, 28 Sep 2020 15:25:26 +0200 Subject: shiboken2: Fix the tests for enums in removed namespaces Change the formatting for better readability and use the "visible" attribute instead of the deprecated "generate". Task-number: PYSIDE-1075 Change-Id: Ibbb33d2482c1d682f74d2edce2dd7d3bb1a50821 Reviewed-by: Christian Tismer --- .../shiboken2/tests/libsample/removednamespaces.h | 36 +++++++++------------- .../tests/samplebinding/typesystem_sample.xml | 6 ++-- 2 files changed, 18 insertions(+), 24 deletions(-) diff --git a/sources/shiboken2/tests/libsample/removednamespaces.h b/sources/shiboken2/tests/libsample/removednamespaces.h index 1de028d38..c9732f8a6 100644 --- a/sources/shiboken2/tests/libsample/removednamespaces.h +++ b/sources/shiboken2/tests/libsample/removednamespaces.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2020 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the test suite of Qt for Python. @@ -34,35 +34,29 @@ namespace RemovedNamespace1 { -enum RemovedNamespace1_Enum { - RemovedNamespace1_Enum_Value0 -}; +enum RemovedNamespace1_Enum { RemovedNamespace1_Enum_Value0 }; -enum { - RemovedNamespace1_AnonymousEnum_Value0 -}; +enum { RemovedNamespace1_AnonymousEnum_Value0 }; -namespace RemovedNamespace2 { - enum RemovedNamespace2_Enum { - RemovedNamespace2_Enum_Value0 - }; -} +namespace RemovedNamespace2 +{ + +enum RemovedNamespace2_Enum { RemovedNamespace2_Enum_Value0 }; -} +} // namespace RemovedNamespace2 +} // namespace RemovedNamespace1 namespace UnremovedNamespace { + namespace RemovedNamespace3 { - enum RemovedNamespace3_Enum { - RemovedNamespace3_Enum_Value0 - }; + enum RemovedNamespace3_Enum { RemovedNamespace3_Enum_Value0 }; + + enum { RemovedNamespace3_AnonymousEnum_Value0 }; - enum { - RemovedNamespace3_AnonymousEnum_Value0 - }; -} -} +} // namespace RemovedNamespace3 +} // namespace UnremovedNamespace #endif // REMOVEDNAMESPACE_H diff --git a/sources/shiboken2/tests/samplebinding/typesystem_sample.xml b/sources/shiboken2/tests/samplebinding/typesystem_sample.xml index 73f8542fc..17d4b11ec 100644 --- a/sources/shiboken2/tests/samplebinding/typesystem_sample.xml +++ b/sources/shiboken2/tests/samplebinding/typesystem_sample.xml @@ -623,16 +623,16 @@ - + - + - + -- cgit v1.2.3 From 50247e7d4a67e892a85dff7472f889bac97c91c7 Mon Sep 17 00:00:00 2001 From: Cristian Maureira-Fredes Date: Mon, 28 Sep 2020 16:28:56 +0200 Subject: doc: fix more sphinx warnings Change-Id: I07a87ad49546534f3492b534b2a2dd4945304de7 Reviewed-by: Christian Tismer --- sources/pyside2/doc/deployment.rst | 2 +- sources/pyside2/doc/faq.rst | 4 ++++ sources/pyside2/doc/gettingstarted.rst | 3 +-- sources/pyside2/doc/index.rst | 4 ++-- sources/pyside2/doc/tutorials/portingguide/chapter3/chapter3.rst | 2 +- sources/shiboken2/doc/index.rst | 4 ++-- sources/shiboken2/doc/typesystem_manipulating_objects.rst | 2 +- sources/shiboken2/doc/typesystem_sequenceprotocol.rst | 2 +- 8 files changed, 13 insertions(+), 10 deletions(-) diff --git a/sources/pyside2/doc/deployment.rst b/sources/pyside2/doc/deployment.rst index 414a468ed..3d6aa218e 100644 --- a/sources/pyside2/doc/deployment.rst +++ b/sources/pyside2/doc/deployment.rst @@ -101,7 +101,7 @@ these tools don't offer a mechanism to update your application packages. To create update packages, use the `PyUpdater `_, which is a tool built around PyInstaller. -The `fbs `_ tool offers a nice UI for the user to install the +The `fbs`_ tool offers a nice UI for the user to install the application step-by-step. .. note:: diff --git a/sources/pyside2/doc/faq.rst b/sources/pyside2/doc/faq.rst index f1aa3ebcc..b7e9816c8 100644 --- a/sources/pyside2/doc/faq.rst +++ b/sources/pyside2/doc/faq.rst @@ -1,3 +1,7 @@ +.. _faq: + +:orphan: + Frequently Asked Questions ========================== diff --git a/sources/pyside2/doc/gettingstarted.rst b/sources/pyside2/doc/gettingstarted.rst index b8d6f9e45..197657493 100644 --- a/sources/pyside2/doc/gettingstarted.rst +++ b/sources/pyside2/doc/gettingstarted.rst @@ -7,10 +7,9 @@ with ``pip`` you need to run:: pip install pyside2 for more details, refer to our `Quick Start`_ guide. Additionally, you can -check the `FAQ`_ related to the project. +check the :ref:`FAQ ` related to the project. .. _Quick Start: quickstart.html -.. _FAQ: faq.html General Requirements -------------------- diff --git a/sources/pyside2/doc/index.rst b/sources/pyside2/doc/index.rst index 289a653f3..ed5aeb6b4 100644 --- a/sources/pyside2/doc/index.rst +++ b/sources/pyside2/doc/index.rst @@ -5,13 +5,13 @@ **Qt for Python** offers the official Python bindings for `Qt`_ (`PySide2`_), so that you can use Qt5 APIs in your Python applications, and a binding generator tool - (`Shiboken2 `_) which can be used to expose C++ projects into Python. + (`Shiboken2 `__) which can be used to expose C++ projects into Python. .. ifconfig:: output_format == 'qthelp' **Qt for Python** offers the official Python bindings for `Qt`_ (`PySide2`_), so that you can use Qt5 APIs in your Python applications, and a binding generator tool - (`Shiboken2 <../shiboken2/index.html>`_) which can be used to expose C++ projects into Python. + (`Shiboken2 <../shiboken2/index.html>`__) which can be used to expose C++ projects into Python. |project| is available under the LGPLv3/GPLv3 and the Qt commercial license. diff --git a/sources/pyside2/doc/tutorials/portingguide/chapter3/chapter3.rst b/sources/pyside2/doc/tutorials/portingguide/chapter3/chapter3.rst index 71b254811..6d7db9ef5 100644 --- a/sources/pyside2/doc/tutorials/portingguide/chapter3/chapter3.rst +++ b/sources/pyside2/doc/tutorials/portingguide/chapter3/chapter3.rst @@ -34,7 +34,7 @@ C++ version .. literalinclude:: bookwindow.cpp :language: c++ :linenos: - :lines: 47-115 + :lines: 57-140 Python version --------------- diff --git a/sources/shiboken2/doc/index.rst b/sources/shiboken2/doc/index.rst index a6e1bccd4..ebde8d1ff 100644 --- a/sources/shiboken2/doc/index.rst +++ b/sources/shiboken2/doc/index.rst @@ -3,11 +3,11 @@ Shiboken .. ifconfig:: output_format == 'html' - Shiboken is a fundamental piece on the `Qt for Python <../index.html>`_ project that serves two purposes: + Shiboken is a fundamental piece on the `Qt for Python <../index.html>`__ project that serves two purposes: .. ifconfig:: output_format == 'qthelp' - Shiboken is a fundamental piece on the `Qt for Python <../pyside2/index.html>`_ project that serves two purposes: + Shiboken is a fundamental piece on the `Qt for Python <../pyside2/index.html>`__ project that serves two purposes: * Generator_: Extract information from C or C++ headers and generate CPython_ code that allow diff --git a/sources/shiboken2/doc/typesystem_manipulating_objects.rst b/sources/shiboken2/doc/typesystem_manipulating_objects.rst index fb0a50aba..d87b816a3 100644 --- a/sources/shiboken2/doc/typesystem_manipulating_objects.rst +++ b/sources/shiboken2/doc/typesystem_manipulating_objects.rst @@ -206,7 +206,7 @@ add-function Within the signature, names for the function parameters can be specified by enclosing them within the delimiter *@*: - .. code-block:: c++ + .. code-block:: void foo(int @parameter1@,float) diff --git a/sources/shiboken2/doc/typesystem_sequenceprotocol.rst b/sources/shiboken2/doc/typesystem_sequenceprotocol.rst index 26ae3b220..742be3d70 100644 --- a/sources/shiboken2/doc/typesystem_sequenceprotocol.rst +++ b/sources/shiboken2/doc/typesystem_sequenceprotocol.rst @@ -22,7 +22,7 @@ function with parameters and return type defined by the table above. The function needs to follow the same semantics of the *CPython equivalent* function, the only way to do it is using the -:doc:`inject-code ` tag. +:ref:`inject-code ` tag. A concrete example how to add sequence protocol support to a class can be found on shiboken tests, more precisely in the definition of the Str class in -- cgit v1.2.3