diff options
17 files changed, 298 insertions, 96 deletions
diff --git a/sources/pyside2/libpyside/pyside.cpp b/sources/pyside2/libpyside/pyside.cpp index 219b99d48..297e5a3b2 100644 --- a/sources/pyside2/libpyside/pyside.cpp +++ b/sources/pyside2/libpyside/pyside.cpp @@ -121,38 +121,36 @@ static bool _setProperty(PyObject *qObj, PyObject *name, PyObject *value, bool * return true; } -bool fillQtProperties(PyObject *qObj, const QMetaObject *metaObj, PyObject *kwds, const char **blackList, unsigned int blackListSize) +bool fillQtProperties(PyObject *qObj, const QMetaObject *metaObj, PyObject *kwds) { PyObject *key, *value; Py_ssize_t pos = 0; while (PyDict_Next(kwds, &pos, &key, &value)) { - if (!blackListSize || !std::binary_search(blackList, blackList + blackListSize, std::string(Shiboken::String::toCString(key)))) { - QByteArray propName(Shiboken::String::toCString(key)); - bool accept = false; - if (metaObj->indexOfProperty(propName) != -1) { - if (!_setProperty(qObj, key, value, &accept)) - return false; - } else { - propName.append("()"); - if (metaObj->indexOfSignal(propName) != -1) { - accept = true; - propName.prepend('2'); - if (!PySide::Signal::connect(qObj, propName, value)) - return false; - } - } - if (!accept) { - // PYSIDE-1019: Allow any existing attribute in the constructor. - if (!_setProperty(qObj, key, value, &accept)) + QByteArray propName(Shiboken::String::toCString(key)); + bool accept = false; + if (metaObj->indexOfProperty(propName) != -1) { + if (!_setProperty(qObj, key, value, &accept)) + return false; + } else { + propName.append("()"); + if (metaObj->indexOfSignal(propName) != -1) { + accept = true; + propName.prepend('2'); + if (!PySide::Signal::connect(qObj, propName, value)) return false; } - if (!accept) { - PyErr_Format(PyExc_AttributeError, "'%s' is not a Qt property or a signal", - propName.constData()); + } + if (!accept) { + // PYSIDE-1019: Allow any existing attribute in the constructor. + if (!_setProperty(qObj, key, value, &accept)) return false; - } + } + if (!accept) { + PyErr_Format(PyExc_AttributeError, "'%s' is not a Qt property or a signal", + propName.constData()); + return false; } } return true; diff --git a/sources/pyside2/libpyside/pyside.h b/sources/pyside2/libpyside/pyside.h index c1a298cc8..a465fec47 100644 --- a/sources/pyside2/libpyside/pyside.h +++ b/sources/pyside2/libpyside/pyside.h @@ -71,12 +71,10 @@ inline Py_ssize_t hash(const T& value) * Fill QObject properties and do signal connections using the values found in \p kwds dictonary. * \param qObj PyObject fot the QObject. * \param metaObj QMetaObject of \p qObj. - * \param blackList keys to be ignored in kwds dictionary, this string list MUST be sorted. - * \param blackListSize numbe rof elements in blackList. * \param kwds key->value dictonary. * \return True if everything goes well, false with a Python error setted otherwise. */ -PYSIDE_API bool fillQtProperties(PyObject *qObj, const QMetaObject *metaObj, PyObject *kwds, const char **blackList, unsigned int blackListSize); +PYSIDE_API bool fillQtProperties(PyObject *qObj, const QMetaObject *metaObj, PyObject *kwds); /** * If the type \p T was registered on Qt meta type system with Q_DECLARE_METATYPE macro, this class will initialize diff --git a/sources/pyside2/libpyside/pysidestaticstrings.cpp b/sources/pyside2/libpyside/pysidestaticstrings.cpp index 760d77632..2dab2caa9 100644 --- a/sources/pyside2/libpyside/pysidestaticstrings.cpp +++ b/sources/pyside2/libpyside/pysidestaticstrings.cpp @@ -60,4 +60,10 @@ STATIC_STRING_IMPL(name, "name") STATIC_STRING_IMPL(property, "property") STATIC_STRING_IMPL(select_id, "select_id") } // namespace PyName +namespace PyMagicName +{ +STATIC_STRING_IMPL(doc, "__doc__") +STATIC_STRING_IMPL(name, "__name__") +STATIC_STRING_IMPL(property_methods, "__property_methods__") +} // namespace PyMagicName } // namespace PySide diff --git a/sources/pyside2/libpyside/pysidestaticstrings.h b/sources/pyside2/libpyside/pysidestaticstrings.h index 1222d8f47..54d1ab9cd 100644 --- a/sources/pyside2/libpyside/pysidestaticstrings.h +++ b/sources/pyside2/libpyside/pysidestaticstrings.h @@ -55,6 +55,12 @@ PyObject *name(); PyObject *property(); PyObject *select_id(); } // namespace PyName +namespace PyMagicName +{ +PyObject *doc(); +PyObject *name(); +PyObject *property_methods(); +} // namespace PyMagicName } // namespace PySide #endif // PYSIDESTRINGS_H diff --git a/sources/pyside2/tests/pysidetest/constructor_properties_test.py b/sources/pyside2/tests/pysidetest/constructor_properties_test.py index 139091fed..5d1027048 100644 --- a/sources/pyside2/tests/pysidetest/constructor_properties_test.py +++ b/sources/pyside2/tests/pysidetest/constructor_properties_test.py @@ -47,11 +47,13 @@ init_test_paths(False) from helper.usesqapplication import UsesQApplication from PySide2.QtCore import Qt -from PySide2.QtWidgets import QApplication, QLabel, QFrame +from PySide2.QtGui import QColor +from PySide2.QtWidgets import QAction, QApplication, QFrame, QLabel class ConstructorPropertiesTest(UsesQApplication): + # PYSIDE-1019: First property extension was support by the constructor. def testCallConstructor(self): label = QLabel( frameStyle=QFrame.Panel | QFrame.Sunken, @@ -65,6 +67,34 @@ class ConstructorPropertiesTest(UsesQApplication): )) +class DiverseKeywordsTest(UsesQApplication): + + def testDuplicateKeyword(self): + r, g, b, a = 1, 2, 3, 4 + with self.assertRaises(TypeError) as cm: + QColor(r, g, b, a, a=0) + self.assertTrue("multiple" in cm.exception.args[0]) + + # PYSIDE-1305: Handle keyword args correctly. + def testUndefinedKeyword(self): + r, g, b, a = 1, 2, 3, 4 + # From the jira issue: + with self.assertRaises(AttributeError) as cm: + QColor(r, g, b, a, alpha=0) + self.assertTrue("unsupported" in cm.exception.args[0]) + + # PYSIDE-1305: Handle keyword args correctly. + def testUndefinedConstructorKeyword(self): + # make sure that the given attribute lands in the constructor + x = QAction(autoRepeat=False) + self.assertEqual(x.autoRepeat(), False) + x = QAction(autoRepeat=True) + self.assertEqual(x.autoRepeat(), True) + # QAction derives from QObject, and so the missing attributes + # in the constructor are reported as AttributeError. + with self.assertRaises(AttributeError): + QAction(some_other_name=42) + + if __name__ == '__main__': unittest.main() - diff --git a/sources/shiboken2/generator/shiboken2/cppgenerator.cpp b/sources/shiboken2/generator/shiboken2/cppgenerator.cpp index 30430c01f..ff44db955 100644 --- a/sources/shiboken2/generator/shiboken2/cppgenerator.cpp +++ b/sources/shiboken2/generator/shiboken2/cppgenerator.cpp @@ -1831,6 +1831,11 @@ void CppGenerator::writeMethodWrapperPreamble(QTextStream &s, OverloadData &over usesNamedArguments = rfunc->isCallOperator() || overloadData.hasArgumentWithDefaultValue(); } + s << INDENT << "PyObject *errInfo{};\n"; + s << INDENT << "SBK_UNUSED(errInfo)\n"; + s << INDENT << "static const char *fullName = \"" + << fullPythonFunctionName(rfunc, true) << "\";\n"; + s << INDENT << "SBK_UNUSED(fullName)\n"; if (maxArgs > 0) { s << INDENT << "int overloadId = -1;\n"; s << INDENT << "PythonToCppFunc " << PYTHON_TO_CPP_VAR; @@ -1866,28 +1871,8 @@ void CppGenerator::writeConstructorWrapper(QTextStream &s, const AbstractMetaFun s << "static int\n"; s << cpythonFunctionName(rfunc) << "(PyObject *self, PyObject *args, PyObject *kwds)\n{\n"; - QSet<QString> argNamesSet; - if (usePySideExtensions() && metaClass->isQObject()) { - // Write argNames variable with all known argument names. - const OverloadData::MetaFunctionList &overloads = overloadData.overloads(); - for (const AbstractMetaFunction *func : overloads) { - const AbstractMetaArgumentList &arguments = func->arguments(); - for (const AbstractMetaArgument *arg : arguments) { - if (arg->defaultValueExpression().isEmpty() || func->argumentRemoved(arg->argumentIndex() + 1)) - continue; - argNamesSet << arg->name(); - } - } - QStringList argNamesList = argNamesSet.values(); - std::sort(argNamesList.begin(), argNamesList.end()); - if (argNamesList.isEmpty()) { - s << INDENT << "const char **argNames{};\n"; - } else { - s << INDENT << "const char *argNames[] = {\"" - << argNamesList.join(QLatin1String("\", \"")) << "\"};\n"; - } + if (usePySideExtensions() && metaClass->isQObject()) s << INDENT << "const QMetaObject *metaObject;\n"; - } s << INDENT << "SbkObject *sbkSelf = reinterpret_cast<SbkObject *>(self);\n"; @@ -1927,6 +1912,8 @@ void CppGenerator::writeConstructorWrapper(QTextStream &s, const AbstractMetaFun { Indentation indent(INDENT); s << INDENT << "delete cptr;\n"; + if (overloadData.maxArgs() > 0) + s << INDENT << "Py_XDECREF(errInfo);\n"; s << INDENT << returnStatement(m_currentErrorCode) << Qt::endl; } s << INDENT << "}\n"; @@ -1947,19 +1934,24 @@ void CppGenerator::writeConstructorWrapper(QTextStream &s, const AbstractMetaFun s << INDENT << "if (Shiboken::BindingManager::instance().hasWrapper(cptr)) {\n"; { Indentation indent(INDENT); - s << INDENT << "Shiboken::BindingManager::instance().releaseWrapper(Shiboken::BindingManager::instance().retrieveWrapper(cptr));\n"; + s << INDENT << "Shiboken::BindingManager::instance().releaseWrapper(" + "Shiboken::BindingManager::instance().retrieveWrapper(cptr));\n"; } s << INDENT << "}\n"; s << INDENT << "Shiboken::BindingManager::instance().registerWrapper(sbkSelf, cptr);\n"; // Create metaObject and register signal/slot + bool errHandlerNeeded = overloadData.maxArgs() > 0; if (metaClass->isQObject() && usePySideExtensions()) { + errHandlerNeeded = true; s << Qt::endl << INDENT << "// QObject setup\n"; s << INDENT << "PySide::Signal::updateSourceObject(self);\n"; s << INDENT << "metaObject = cptr->metaObject(); // <- init python qt properties\n"; - s << INDENT << "if (kwds && !PySide::fillQtProperties(self, metaObject, kwds, argNames, " - << argNamesSet.count() << "))\n" << indent(INDENT) - << INDENT << returnStatement(m_currentErrorCode) << '\n' << outdent(INDENT); + s << INDENT << "if (errInfo && PyDict_Check(errInfo)) {\n" << indent(INDENT) + << INDENT << "if (!PySide::fillQtProperties(self, metaObject, errInfo))\n" << indent(INDENT) + << INDENT << "goto " << cpythonFunctionName(rfunc) << "_TypeError;\n" << outdent(INDENT) + << INDENT << "Py_DECREF(errInfo);\n" << outdent(INDENT) + << INDENT << "};\n"; } // Constructor code injections, position=end @@ -1997,7 +1989,7 @@ void CppGenerator::writeConstructorWrapper(QTextStream &s, const AbstractMetaFun s << Qt::endl; s << Qt::endl << INDENT << "return 1;\n"; - if (overloadData.maxArgs() > 0) + if (errHandlerNeeded) writeErrorSection(s, overloadData); s<< "}\n\n"; } @@ -2149,8 +2141,11 @@ void CppGenerator::writeArgumentsInitializer(QTextStream &s, OverloadData &overl s << INDENT << "if (numArgs > " << maxArgs << ") {\n"; { Indentation indent(INDENT); - s << INDENT << "PyErr_SetString(PyExc_TypeError, \"" << fullPythonFunctionName(rfunc) << "(): too many arguments\");\n"; - s << INDENT << returnStatement(m_currentErrorCode) << Qt::endl; + s << INDENT << "static PyObject *const too_many = " + "Shiboken::String::createStaticString(\">\");\n"; + s << INDENT << "errInfo = too_many;\n"; + s << INDENT << "Py_INCREF(errInfo);\n"; + s << INDENT << "goto " << cpythonFunctionName(rfunc) << "_TypeError;\n"; } s << INDENT << '}'; } @@ -2162,8 +2157,11 @@ void CppGenerator::writeArgumentsInitializer(QTextStream &s, OverloadData &overl s << "if (numArgs < " << minArgs << ") {\n"; { Indentation indent(INDENT); - s << INDENT << "PyErr_SetString(PyExc_TypeError, \"" << fullPythonFunctionName(rfunc) << "(): not enough arguments\");\n"; - s << INDENT << returnStatement(m_currentErrorCode) << Qt::endl; + s << INDENT << "static PyObject *const too_few = " + "Shiboken::String::createStaticString(\"<\");\n"; + s << INDENT << "errInfo = too_few;\n"; + s << INDENT << "Py_INCREF(errInfo);\n"; + s << INDENT << "goto " << cpythonFunctionName(rfunc) << "_TypeError;\n"; } s << INDENT << '}'; } @@ -2297,11 +2295,13 @@ void CppGenerator::writeErrorSection(QTextStream &s, OverloadData &overloadData) const AbstractMetaFunction *rfunc = overloadData.referenceFunction(); s << Qt::endl << INDENT << cpythonFunctionName(rfunc) << "_TypeError:\n"; Indentation indentation(INDENT); - QString funcName = fullPythonFunctionName(rfunc); + QString funcName = fullPythonFunctionName(rfunc, true); QString argsVar = pythonFunctionWrapperUsesListOfArguments(overloadData) ? QLatin1String("args") : QLatin1String(PYTHON_ARG); - s << INDENT << "Shiboken::setErrorAboutWrongArguments(" << argsVar << ", \"" << funcName << "\");\n"; + s << INDENT << "Shiboken::setErrorAboutWrongArguments(" << argsVar + << ", fullName, errInfo);\n"; + s << INDENT << "Py_XDECREF(errInfo);\n"; s << INDENT << "return " << m_currentErrorCode << ";\n"; } @@ -2905,7 +2905,7 @@ void CppGenerator::writeSingleFunctionCall(QTextStream &s, bool usePyArgs = pythonFunctionWrapperUsesListOfArguments(overloadData); // Handle named arguments. - writeNamedArgumentResolution(s, func, usePyArgs); + writeNamedArgumentResolution(s, func, usePyArgs, overloadData); bool injectCodeCallsFunc = injectedCodeCallsCppFunction(context, func); bool mayHaveUnunsedArguments = !func->isUserAdded() && func->hasInjectedCode() && injectCodeCallsFunc; @@ -3228,33 +3228,46 @@ void CppGenerator::writeAddPythonToCppConversion(QTextStream &s, const QString & s << ");\n"; } -void CppGenerator::writeNamedArgumentResolution(QTextStream &s, const AbstractMetaFunction *func, bool usePyArgs) +void CppGenerator::writeNamedArgumentResolution(QTextStream &s, const AbstractMetaFunction *func, + bool usePyArgs, const OverloadData &overloadData) { const AbstractMetaArgumentList &args = OverloadData::getArgumentsWithDefaultValues(func); - if (args.isEmpty()) + if (args.isEmpty()) { + if (overloadData.hasArgumentWithDefaultValue()) { + s << INDENT << "if (kwds) {\n"; + { + Indentation indent(INDENT); + s << INDENT << "errInfo = kwds;\n"; + s << INDENT << "Py_INCREF(errInfo);\n"; + s << INDENT << "goto " << cpythonFunctionName(func) << "_TypeError;\n"; + } + s << INDENT << "}\n"; + } return; - - QString pyErrString(QLatin1String("PyErr_SetString(PyExc_TypeError, \"") + fullPythonFunctionName(func) - + QLatin1String("(): got multiple values for keyword argument '%1'.\");")); + } s << INDENT << "if (kwds) {\n"; { Indentation indent(INDENT); - s << INDENT << "PyObject *keyName = nullptr;\n"; - s << INDENT << "PyObject *value = nullptr;\n"; + s << INDENT << "PyObject *value{};\n"; + s << INDENT << "PyObject *kwds_dup = PyDict_Copy(kwds);\n"; for (const AbstractMetaArgument *arg : args) { - int pyArgIndex = arg->argumentIndex() - OverloadData::numberOfRemovedArguments(func, arg->argumentIndex()); + const int pyArgIndex = arg->argumentIndex() + - OverloadData::numberOfRemovedArguments(func, arg->argumentIndex()); QString pyArgName = usePyArgs ? pythonArgsAt(pyArgIndex) : QLatin1String(PYTHON_ARG); - s << INDENT << "keyName = Py_BuildValue(\"s\",\"" << arg->name() << "\");\n"; - s << INDENT << "if (PyDict_Contains(kwds, keyName)) {\n"; + QString pyKeyName = QLatin1String("key_") + arg->name(); + s << INDENT << "static PyObject *const " << pyKeyName + << " = Shiboken::String::createStaticString(\"" << arg->name() << "\");\n"; + s << INDENT << "if (PyDict_Contains(kwds, " << pyKeyName << ")) {\n"; { Indentation indent(INDENT); - s << INDENT << "value = PyDict_GetItem(kwds, keyName);\n"; + s << INDENT << "value = PyDict_GetItem(kwds, " << pyKeyName << ");\n"; s << INDENT << "if (value && " << pyArgName << ") {\n"; { Indentation indent(INDENT); - s << INDENT << pyErrString.arg(arg->name()) << Qt::endl; - s << INDENT << returnStatement(m_currentErrorCode) << Qt::endl; + s << INDENT << "errInfo = " << pyKeyName << ";\n"; + s << INDENT << "Py_INCREF(errInfo);\n"; + s << INDENT << "goto " << cpythonFunctionName(func) << "_TypeError;\n"; } s << INDENT << "}\n"; s << INDENT << "if (value) {\n"; @@ -3262,7 +3275,8 @@ void CppGenerator::writeNamedArgumentResolution(QTextStream &s, const AbstractMe Indentation indent(INDENT); s << INDENT << pyArgName << " = value;\n"; s << INDENT << "if (!"; - writeTypeCheck(s, arg->type(), pyArgName, isNumber(arg->type()->typeEntry()), func->typeReplaced(arg->argumentIndex() + 1)); + writeTypeCheck(s, arg->type(), pyArgName, isNumber(arg->type()->typeEntry()), + func->typeReplaced(arg->argumentIndex() + 1)); s << ")\n"; { Indentation indent(INDENT); @@ -3270,9 +3284,29 @@ void CppGenerator::writeNamedArgumentResolution(QTextStream &s, const AbstractMe } } s << INDENT << "}\n"; + s << INDENT << "PyDict_DelItem(kwds_dup, " << pyKeyName << ");\n"; } s << INDENT << "}\n"; } + // PYSIDE-1305: Handle keyword args correctly. + // Normal functions handle their parameters immediately. + // For constructors that are QObject, we need to delay that + // until extra keyword signals and properties are handled. + s << INDENT << "if (PyDict_Size(kwds_dup) > 0) {\n"; + { + Indentation indent(INDENT); + s << INDENT << "errInfo = kwds_dup;\n"; + if (!(func->isConstructor() && func->ownerClass()->isQObject())) + s << INDENT << "goto " << cpythonFunctionName(func) << "_TypeError;\n"; + else + s << INDENT << "// fall through to handle extra keyword signals and properties\n"; + } + s << INDENT << "} else {\n"; + { + Indentation indent(INDENT); + s << INDENT << "Py_DECREF(kwds_dup);\n"; + } + s << INDENT << "}\n"; } s << INDENT << "}\n"; } @@ -4892,7 +4926,7 @@ void CppGenerator::writeSignatureInfo(QTextStream &s, const AbstractMetaFunction { OverloadData overloadData(overloads, this); const AbstractMetaFunction *rfunc = overloadData.referenceFunction(); - QString funcName = fullPythonFunctionName(rfunc); + QString funcName = fullPythonFunctionName(rfunc, false); int idx = overloads.length() - 1; bool multiple = idx > 0; diff --git a/sources/shiboken2/generator/shiboken2/cppgenerator.h b/sources/shiboken2/generator/shiboken2/cppgenerator.h index 41bd17f21..25bb51ef5 100644 --- a/sources/shiboken2/generator/shiboken2/cppgenerator.h +++ b/sources/shiboken2/generator/shiboken2/cppgenerator.h @@ -247,7 +247,8 @@ private: void writeAddPythonToCppConversion(QTextStream &s, const QString &converterVar, const QString &pythonToCppFunc, const QString &isConvertibleFunc); - void writeNamedArgumentResolution(QTextStream &s, const AbstractMetaFunction *func, bool usePyArgs); + void writeNamedArgumentResolution(QTextStream &s, const AbstractMetaFunction *func, + bool usePyArgs, const OverloadData &overloadData); /// Returns a string containing the name of an argument for the given function and argument index. QString argumentNameFromIndex(const AbstractMetaFunction *func, int argIndex, const AbstractMetaClass **wrappedClass); diff --git a/sources/shiboken2/generator/shiboken2/shibokengenerator.cpp b/sources/shiboken2/generator/shiboken2/shibokengenerator.cpp index 5d8685b90..0f5f09d60 100644 --- a/sources/shiboken2/generator/shiboken2/shibokengenerator.cpp +++ b/sources/shiboken2/generator/shiboken2/shibokengenerator.cpp @@ -356,7 +356,7 @@ QString ShibokenGenerator::fullPythonClassName(const AbstractMetaClass *metaClas return fullClassName; } -QString ShibokenGenerator::fullPythonFunctionName(const AbstractMetaFunction *func) +QString ShibokenGenerator::fullPythonFunctionName(const AbstractMetaFunction *func, bool forceFunc) { QString funcName; if (func->isOperatorOverload()) @@ -365,10 +365,14 @@ QString ShibokenGenerator::fullPythonFunctionName(const AbstractMetaFunction *fu funcName = func->name(); if (func->ownerClass()) { QString fullClassName = fullPythonClassName(func->ownerClass()); - if (func->isConstructor()) + if (func->isConstructor()) { funcName = fullClassName; - else + if (forceFunc) + funcName.append(QLatin1String(".__init__")); + } + else { funcName.prepend(fullClassName + QLatin1Char('.')); + } } else { funcName = packageName() + QLatin1Char('.') + func->name(); diff --git a/sources/shiboken2/generator/shiboken2/shibokengenerator.h b/sources/shiboken2/generator/shiboken2/shibokengenerator.h index 6d36026cd..cbe796313 100644 --- a/sources/shiboken2/generator/shiboken2/shibokengenerator.h +++ b/sources/shiboken2/generator/shiboken2/shibokengenerator.h @@ -234,7 +234,7 @@ protected: QString wrapperName(const AbstractMetaClass *metaClass) const; QString fullPythonClassName(const AbstractMetaClass *metaClass); - QString fullPythonFunctionName(const AbstractMetaFunction *func); + QString fullPythonFunctionName(const AbstractMetaFunction *func, bool forceFunc); bool wrapperDiagnostics() const { return m_wrapperDiagnostics; } diff --git a/sources/shiboken2/libshiboken/basewrapper.cpp b/sources/shiboken2/libshiboken/basewrapper.cpp index d866d133c..4b1e6e564 100644 --- a/sources/shiboken2/libshiboken/basewrapper.cpp +++ b/sources/shiboken2/libshiboken/basewrapper.cpp @@ -968,9 +968,10 @@ void init() } // setErrorAboutWrongArguments now gets overload info from the signature module. -void setErrorAboutWrongArguments(PyObject *args, const char *funcName) +// Info can be nullptr and contains extra info. +void setErrorAboutWrongArguments(PyObject *args, const char *funcName, PyObject *info) { - SetError_Argument(args, funcName); + SetError_Argument(args, funcName, info); } class FindBaseTypeVisitor : public HierarchyVisitor diff --git a/sources/shiboken2/libshiboken/basewrapper.h b/sources/shiboken2/libshiboken/basewrapper.h index 31083522b..204c4c1c3 100644 --- a/sources/shiboken2/libshiboken/basewrapper.h +++ b/sources/shiboken2/libshiboken/basewrapper.h @@ -157,8 +157,10 @@ void callCppDestructor(void *cptr) delete reinterpret_cast<T *>(cptr); } -// setErrorAboutWrongArguments now gets overload info from the signature module. -LIBSHIBOKEN_API void setErrorAboutWrongArguments(PyObject *args, const char *funcName); +// setErrorAboutWrongArguments now gets overload information from the signature module. +// The extra info argument can contain additional data about the error. +LIBSHIBOKEN_API void setErrorAboutWrongArguments(PyObject *args, const char *funcName, + PyObject *info); namespace ObjectType { diff --git a/sources/shiboken2/libshiboken/sbkstaticstrings.cpp b/sources/shiboken2/libshiboken/sbkstaticstrings.cpp index 5559d58d6..2c1a9b891 100644 --- a/sources/shiboken2/libshiboken/sbkstaticstrings.cpp +++ b/sources/shiboken2/libshiboken/sbkstaticstrings.cpp @@ -87,6 +87,7 @@ STATIC_STRING_IMPL(get, "__get__") STATIC_STRING_IMPL(members, "__members__") STATIC_STRING_IMPL(module, "__module__") STATIC_STRING_IMPL(name, "__name__") +STATIC_STRING_IMPL(property_methods, "__property_methods__") STATIC_STRING_IMPL(qualname, "__qualname__") STATIC_STRING_IMPL(self, "__self__") diff --git a/sources/shiboken2/libshiboken/sbkstaticstrings.h b/sources/shiboken2/libshiboken/sbkstaticstrings.h index b72fa989b..0dd533e32 100644 --- a/sources/shiboken2/libshiboken/sbkstaticstrings.h +++ b/sources/shiboken2/libshiboken/sbkstaticstrings.h @@ -73,6 +73,7 @@ LIBSHIBOKEN_API PyObject *get(); LIBSHIBOKEN_API PyObject *members(); LIBSHIBOKEN_API PyObject *module(); LIBSHIBOKEN_API PyObject *name(); +LIBSHIBOKEN_API PyObject *property_methods(); LIBSHIBOKEN_API PyObject *qualname(); LIBSHIBOKEN_API PyObject *self(); } // namespace PyMagicName diff --git a/sources/shiboken2/libshiboken/signature.h b/sources/shiboken2/libshiboken/signature.h index b77cc0f4c..0459d8661 100644 --- a/sources/shiboken2/libshiboken/signature.h +++ b/sources/shiboken2/libshiboken/signature.h @@ -45,7 +45,7 @@ extern "C" LIBSHIBOKEN_API int InitSignatureStrings(PyTypeObject *, const char *[]); LIBSHIBOKEN_API void FinishSignatureInitialization(PyObject *, const char *[]); -LIBSHIBOKEN_API void SetError_Argument(PyObject *, const char *); +LIBSHIBOKEN_API void SetError_Argument(PyObject *, const char *, PyObject *); LIBSHIBOKEN_API PyObject *Sbk_TypeGet___signature__(PyObject *, PyObject *); LIBSHIBOKEN_API PyObject *Sbk_TypeGet___doc__(PyObject *); LIBSHIBOKEN_API PyObject *GetFeatureDict(); diff --git a/sources/shiboken2/libshiboken/signature/signature.cpp b/sources/shiboken2/libshiboken/signature/signature.cpp index 085d751aa..1f36a09f3 100644 --- a/sources/shiboken2/libshiboken/signature/signature.cpp +++ b/sources/shiboken2/libshiboken/signature/signature.cpp @@ -209,8 +209,12 @@ PyObject *GetSignature_Wrapper(PyObject *ob, PyObject *modifier) if (dict == nullptr) return nullptr; PyObject *props = PyDict_GetItem(dict, func_name); - if (props == nullptr) + if (props == nullptr) { + // handle `__init__` like the class itself + if (strcmp(String::toCString(func_name), "__init__") == 0) + return GetSignature_TypeMod(objclass, modifier); Py_RETURN_NONE; + } return _GetSignature_Cached(props, PyName::method(), modifier); } @@ -431,7 +435,86 @@ void FinishSignatureInitialization(PyObject *module, const char *signatures[]) } } -void SetError_Argument(PyObject *args, const char *func_name) +static PyObject *adjustFuncName(const char *func_name) +{ + /* + * PYSIDE-1019: Modify the function name expression according to feature. + * + * - snake_case + * The function name must be converted. + * - full_property + * The property name must be used and "fset" appended. + * + * modname.subname.classsname.propname.fset + * + * Class properties must use the expression + * + * modname.subname.classsname.__dict__['propname'].fset + * + * Note that fget is impossible because there are no parameters. + */ + static const char mapping_name[] = "shibokensupport.signature.mapping"; + static PyObject *sys_modules = PySys_GetObject("modules"); + static PyObject *mapping = PyDict_GetItemString(sys_modules, mapping_name); + static PyObject *ns = PyModule_GetDict(mapping); + + char _path[200 + 1] = {}; + const char *_name = strrchr(func_name, '.'); + strncat(_path, func_name, _name - func_name); + ++_name; + + // This is a very cheap call into `mapping.py`. + PyObject *update_mapping = PyDict_GetItemString(ns, "update_mapping"); + AutoDecRef res(PyObject_CallFunctionObjArgs(update_mapping, nullptr)); + if (res.isNull()) + return nullptr; + + // Run `eval` on the type string to get the object. + AutoDecRef obtype(PyRun_String(_path, Py_eval_input, ns, ns)); + if (PyModule_Check(obtype.object())) { + // This is a plain function. Return the unmangled name. + return String::fromCString(func_name); + } + assert(PyType_Check(obtype)); // This was not true for __init__! + + // Find the feature flags + auto type = reinterpret_cast<PyTypeObject *>(obtype.object()); + auto dict = type->tp_dict; + int id = SbkObjectType_GetReserved(type); + id = id < 0 ? 0 : id; // if undefined, set to zero + auto lower = id & 0x01; + auto is_prop = id & 0x02; + bool is_class_prop = false; + + // Compute all needed info. + PyObject *name = String::getSnakeCaseName(_name, lower); + PyObject *prop_name; + if (is_prop) { + PyObject *prop_methods = PyDict_GetItem(dict, PyMagicName::property_methods()); + prop_name = PyDict_GetItem(prop_methods, name); + if (prop_name != nullptr) { + PyObject *prop = PyDict_GetItem(dict, prop_name); + is_class_prop = Py_TYPE(prop) != &PyProperty_Type; + } + } + + // Finally, generate the correct path expression. + char _buf[200 + 1] = {}; + if (is_prop) { + auto _prop_name = String::toCString(prop_name); + if (is_class_prop) + sprintf(_buf, "%s.__dict__['%s'].fset", _path, _prop_name); + else + sprintf(_buf, "%s.%s.fset", _path, _prop_name); + } + else { + auto _name = String::toCString(name); + sprintf(_buf, "%s.%s", _path, _name); + } + return String::fromCString(_buf); +} + +void SetError_Argument(PyObject *args, const char *func_name, PyObject *info) { /* * This function replaces the type error construction with extra @@ -440,8 +523,26 @@ void SetError_Argument(PyObject *args, const char *func_name) */ init_module_1(); init_module_2(); - AutoDecRef res(PyObject_CallFunction(pyside_globals->seterror_argument_func, - const_cast<char *>("(Os)"), args, func_name)); + + // PYSIDE-1305: Handle errors set by fillQtProperties. + PyObject *err_val{}; + if (PyErr_Occurred()) { + PyObject *e, *t; + PyErr_Fetch(&e, &err_val, &t); + info = err_val; + Py_XDECREF(&e); + Py_XDECREF(&t); + } + // PYSIDE-1019: Modify the function name expression according to feature. + AutoDecRef new_func_name(adjustFuncName(func_name)); + if (new_func_name.isNull()) { + PyErr_Print(); + Py_FatalError("seterror_argument failed to call update_mapping"); + } + if (info == nullptr) + info = Py_None; + AutoDecRef res(PyObject_CallFunctionObjArgs(pyside_globals->seterror_argument_func, + args, new_func_name.object(), info, nullptr)); if (res.isNull()) { PyErr_Print(); Py_FatalError("seterror_argument did not receive a result"); @@ -451,6 +552,7 @@ void SetError_Argument(PyObject *args, const char *func_name) PyErr_Print(); Py_FatalError("unexpected failure in seterror_argument"); } + Py_XDECREF(err_val); PyErr_SetObject(err, msg); } diff --git a/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/signature/errorhandler.py b/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/signature/errorhandler.py index 6ed4c0edd..352644f7a 100644 --- a/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/signature/errorhandler.py +++ b/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/signature/errorhandler.py @@ -95,15 +95,33 @@ def matched_type(args, sigs): return None -def seterror_argument(args, func_name): - update_mapping() +def seterror_argument(args, func_name, info): func = None try: func = eval(func_name, namespace) except Exception as e: - msg = "Internal error evaluating " + func_name + " :" + str(e) + msg = "Internal error evaluating {func_name}: {e}".format(**locals()) return TypeError, msg + if info and type(info) is str: + err = TypeError + if info == "<": + msg = "{func_name}(): not enough arguments".format(**locals()) + elif info == ">": + msg = "{func_name}(): too many arguments".format(**locals()) + elif info.isalnum(): + msg = "{func_name}(): got multiple values for keyword argument '{info}'".format(**locals()) + else: + msg = "{func_name}(): {info}".format(**locals()) + err = AttributeError + return err, msg + if info and type(info) is dict: + keyword = tuple(info)[0] + msg = "{func_name}(): unsupported keyword '{keyword}'".format(**locals()) + return AttributeError, msg sigs = get_signature(func, "typeerror") + if not sigs: + msg = "{func_name}({args}) is wrong (missing signature)".format(**locals()) + return TypeError, msg if type(sigs) != list: sigs = [sigs] if type(args) != tuple: @@ -144,7 +162,7 @@ def make_helptext(func): sigs = [sigs] try: func_name = func.__name__ - except AttribureError: + except AttributeError: func_name = func.__func__.__name__ sigtext = "\n".join(func_name + str(sig) for sig in sigs) msg = sigtext + "\n\n" + existing_doc if check_string_type(existing_doc) else sigtext diff --git a/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/signature/loader.py b/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/signature/loader.py index 6cee54680..a6c3e420d 100644 --- a/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/signature/loader.py +++ b/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/signature/loader.py @@ -101,8 +101,8 @@ def create_signature(props, key): return layout.create_signature(props, key) # name used in signature.cpp -def seterror_argument(args, func_name): - return errorhandler.seterror_argument(args, func_name) +def seterror_argument(args, func_name, info): + return errorhandler.seterror_argument(args, func_name, info) # name used in signature.cpp def make_helptext(func): |