diff options
Diffstat (limited to 'sources/shiboken6/generator/shiboken')
16 files changed, 13821 insertions, 0 deletions
diff --git a/sources/shiboken6/generator/shiboken/configurablescope.h b/sources/shiboken6/generator/shiboken/configurablescope.h new file mode 100644 index 000000000..9040c7ad9 --- /dev/null +++ b/sources/shiboken6/generator/shiboken/configurablescope.h @@ -0,0 +1,33 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef CONFIGURABLESCOPE_H +#define CONFIGURABLESCOPE_H + +#include <textstream.h> +#include <configurabletypeentry.h> + +/// Enclose a scope within preprocessor conditions for configurable entries +class ConfigurableScope +{ +public: + explicit ConfigurableScope(TextStream &s, const ConfigurableTypeEntryCPtr &t) : + m_stream(s), + m_hasConfigCondition(t->hasConfigCondition()) + { + if (m_hasConfigCondition) + m_stream << t->configCondition() << '\n'; + } + + ~ConfigurableScope() + { + if (m_hasConfigCondition) + m_stream << "#endif\n"; + } + +private: + TextStream &m_stream; + const bool m_hasConfigCondition; +}; + +#endif // CONFIGURABLESCOPE_H diff --git a/sources/shiboken6/generator/shiboken/cppgenerator.cpp b/sources/shiboken6/generator/shiboken/cppgenerator.cpp new file mode 100644 index 000000000..0eeeec6f9 --- /dev/null +++ b/sources/shiboken6/generator/shiboken/cppgenerator.cpp @@ -0,0 +1,6820 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "cppgenerator.h" +#include "configurablescope.h" +#include "generatorargument.h" +#include "generatorstrings.h" +#include "defaultvalue.h" +#include "generatorcontext.h" +#include "codesnip.h" +#include "customconversion.h" +#include "headergenerator.h" +#include "apiextractorresult.h" +#include "ctypenames.h" +#include <exception.h> +#include "pytypenames.h" +#include "fileout.h" +#include "overloaddata.h" +#include "pymethoddefentry.h" +#include <abstractmetaenum.h> +#include <abstractmetafield.h> +#include <abstractmetafunction.h> +#include <abstractmetalang.h> +#include <abstractmetalang_helpers.h> +#include <messages.h> +#include <modifications.h> +#include <propertyspec.h> +#include <reporthandler.h> +#include <sourcelocation.h> +#include <textstream.h> +#include <typedatabase.h> +#include <containertypeentry.h> +#include <enumtypeentry.h> +#include <flagstypeentry.h> +#include <functiontypeentry.h> +#include <namespacetypeentry.h> +#include <primitivetypeentry.h> +#include <smartpointertypeentry.h> +#include <typesystemtypeentry.h> +#include <valuetypeentry.h> +#include <parser/enumvalue.h> + +#include "qtcompat.h" + +#include <QtCore/QDebug> +#include <QtCore/QDir> +#include <QtCore/QMetaObject> +#include <QtCore/QMetaType> +#include <QtCore/QRegularExpression> +#include <QtCore/QTextStream> + +#include <algorithm> +#include <cstring> +#include <memory> +#include <set> + +using namespace Qt::StringLiterals; + +static const char shibokenErrorsOccurred[] = "Shiboken::Errors::occurred() != nullptr"; + +static constexpr auto virtualMethodStaticReturnVar = "result"_L1; + +static constexpr auto sbkObjectTypeF = "SbkObject_TypeF()"_L1; +static const char initInheritanceFunction[] = "initInheritance"; + +static QString mangleName(QString name) +{ + if (name == u"None" || name == u"False" || name == u"True" || name == u"from") + name += u'_'; + return name; +} + +struct sbkUnusedVariableCast +{ + explicit sbkUnusedVariableCast(QAnyStringView name) : m_name(name) {} + + const QAnyStringView m_name; +}; + +TextStream &operator<<(TextStream &str, const sbkUnusedVariableCast &c) +{ + str << "SBK_UNUSED(" << c.m_name << ")\n"; + return str; +} + +struct pyTypeGetSlot +{ + explicit pyTypeGetSlot(QAnyStringView funcType, QAnyStringView typeObject, + QAnyStringView aSlot) : + m_funcType(funcType), m_typeObject(typeObject), m_slot(aSlot) {} + + const QAnyStringView m_funcType; + const QAnyStringView m_typeObject; + const QAnyStringView m_slot; +}; + +TextStream &operator<<(TextStream &str, const pyTypeGetSlot &p) +{ + str << "reinterpret_cast<" << p.m_funcType << ">(PepType_GetSlot(" + << p.m_typeObject << ", " << p.m_slot << "));\n"; + return str; +} + +TextStream &operator<<(TextStream &s, CppGenerator::ErrorReturn r) +{ + s << "return"; + switch (r) { + case CppGenerator::ErrorReturn::Default: + s << " {}"; + break; + case CppGenerator::ErrorReturn::Zero: + s << " 0"; + break; + case CppGenerator::ErrorReturn::MinusOne: + s << " -1"; + break; + case CppGenerator::ErrorReturn::Void: + break; + } + s << ";\n"; + return s; +} + +static constexpr auto converterVar = "converter"_L1; + +struct registerConverterName +{ + explicit registerConverterName(QAnyStringView typeName, + QAnyStringView varName = converterVar) : + m_typeName(typeName), m_varName(varName) {} + + QAnyStringView m_typeName; + QAnyStringView m_varName; +}; + +TextStream &operator<<(TextStream &s, const registerConverterName &r) +{ + s << "Shiboken::Conversions::registerConverterName(" << r.m_varName + << ", \"" << r.m_typeName << "\");\n"; + return s; +} + +// Protocol function name / function parameters / return type +struct ProtocolEntry +{ + QString name; + QString arguments; + QString returnType; +}; + +using ProtocolEntries = QList<ProtocolEntry>; + +static bool contains(const ProtocolEntries &l, const QString &needle) +{ + for (const auto &m : l) { + if (m.name == needle) + return true; + } + return false; +} + +// Maps special function names to function parameters and return types +// used by CPython API in the mapping protocol. +const ProtocolEntries &mappingProtocols() +{ + static const ProtocolEntries result = { + {u"__mlen__"_s, + u"PyObject *self"_s, + u"Py_ssize_t"_s}, + {u"__mgetitem__"_s, + u"PyObject *self, PyObject *_key"_s, + u"PyObject*"_s}, + {u"__msetitem__"_s, + u"PyObject *self, PyObject *_key, PyObject *_value"_s, + intT}}; + return result; +} + +// Maps special function names to function parameters and return types +// used by CPython API in the sequence protocol. +const ProtocolEntries &sequenceProtocols() +{ + static const ProtocolEntries result = { + {u"__len__"_s, + u"PyObject *self"_s, + u"Py_ssize_t"_s}, + {u"__getitem__"_s, + u"PyObject *self, Py_ssize_t _i"_s, + u"PyObject*"_s}, + {u"__setitem__"_s, + u"PyObject *self, Py_ssize_t _i, PyObject *_value"_s, + intT}, + {u"__getslice__"_s, + u"PyObject *self, Py_ssize_t _i1, Py_ssize_t _i2"_s, + u"PyObject*"_s}, + {u"__setslice__"_s, + u"PyObject *self, Py_ssize_t _i1, Py_ssize_t _i2, PyObject *_value"_s, + intT}, + {u"__contains__"_s, + u"PyObject *self, PyObject *_value"_s, + intT}, + {u"__concat__"_s, + u"PyObject *self, PyObject *_other"_s, + u"PyObject*"_s} + }; + return result; +} + +// Return name of function to create PyObject wrapping a container +static QString opaqueContainerCreationFunc(const AbstractMetaType &type) +{ + const auto containerTypeEntry = + std::static_pointer_cast<const ContainerTypeEntry>(type.typeEntry()); + const auto instantiationTypeEntry = + type.instantiations().constFirst().typeEntry(); + QString result = u"create"_s; + if (type.isConstant()) + result += u"Const"_s; + result += containerTypeEntry->opaqueContainerName(type.instantiationCppSignatures()); + return result; +} + +// Write declaration of the function to create PyObject wrapping a container +static void writeOpaqueContainerCreationFuncDecl(TextStream &s, const QString &name, + AbstractMetaType type) +{ + type.setReferenceType(NoReference); + // Maintain const + s << "PyObject *" << name << '(' << type.cppSignature() << "*);\n"; +} + +CppGenerator::CppGenerator() = default; + +QString CppGenerator::fileNameForContext(const GeneratorContext &context) const +{ + return fileNameForContextHelper(context, u"_wrapper.cpp"_s); +} + +void CppGenerator::clearTpFuncs() +{ + // Functions that should not be registered under a name in PyMethodDef, + // but under a special constant under slots. + m_tpFuncs = { + {u"__str__"_s, {}}, {u"__str__"_s, {}}, + {REPR_FUNCTION, {}}, {u"__iter__"_s, {}}, + {u"__next__"_s, {}} + }; + m_nbFuncs = { {u"__abs__"_s, {}}, {u"__pow__"_s, {} }}; +} + +// Prevent ELF symbol qt_version_tag from being generated into the source +static const char includeQDebug[] = +"#ifndef QT_NO_VERSION_TAGGING\n" +"# define QT_NO_VERSION_TAGGING\n" +"#endif\n" +"#include <QtCore/QDebug>\n"; + +QString CppGenerator::chopType(QString s) +{ + if (s.endsWith(u"_Type")) + s.chop(5); + else if (s.endsWith(u"_TypeF()")) + s.chop(8); + return s; +} + +static bool isStdSetterName(QString setterName, QString propertyName) +{ + return setterName.size() == propertyName.size() + 3 + && setterName.startsWith(u"set") + && setterName.endsWith(QStringView{propertyName}.right(propertyName.size() - 1)) + && setterName.at(3) == propertyName.at(0).toUpper(); +} + +static QString buildPropertyString(const QPropertySpec &spec) +{ + QString text = u'"' + spec.name() + u':'; + + if (spec.read() != spec.name()) + text += spec.read(); + + if (!spec.write().isEmpty()) { + text += u':'; + if (!isStdSetterName(spec.write(), spec.name())) + text += spec.write(); + } + + text += u'"'; + return text; +} + +static QString _plainName(const QString &s) +{ + auto cutPos = s.lastIndexOf(u"::"_s); + return cutPos < 0 ? s : s.right(s.length() - (cutPos + 2)); +} + +/********************************************************************** + * + * Decision whether to use an IntEnum/IntFlag + * ------------------------------------------ + * + * Unfortunately, all attempts to drive this decision automagically + * did not work out. We therefore compile a list in with known + * IntEnum and IntFlag. + */ + +/* + * This function is now unused and replaced by TypeSystem::PythonEnumType + */ +#if 0 +static QSet<QString> useIntSet() +{ + static const QSet<QString> result{ + /* IntEnum */ u"PySide6.QtCore.QDataStream.Version"_s, + /* IntEnum */ u"PySide6.QtCore.QEvent.Type"_s, + /* IntEnum */ u"PySide6.QtCore.QLocale.FloatingPointPrecisionOption"_s, + /* IntFlag */ u"PySide6.QtCore.QLocale.LanguageCodeType"_s, + /* IntFlag */ u"PySide6.QtCore.QUrl.ComponentFormattingOption"_s, + // note: "QUrl::UrlFormattingOption" is set as IntFlag without flags + /* IntFlag */ u"PySide6.QtCore.QUrl.UrlFormattingOption"_s, + /* IntFlag */ u"PySide6.QtCore.Qt.AlignmentFlag"_s, + /* IntFlag */ u"PySide6.QtCore.Qt.FocusPolicy"_s, + /* IntEnum */ u"PySide6.QtCore.Qt.GestureType"_s, + /* IntEnum */ u"PySide6.QtCore.Qt.ItemDataRole"_s, + /* IntEnum */ u"PySide6.QtCore.Qt.Key"_s, + /* Flag */ u"PySide6.QtCore.Qt.Modifier"_s, + // note: "Qt::TextFlag" is set as IntFlag without flags + /* IntFlag */ u"PySide6.QtCore.Qt.TextFlag"_s, + /* IntFlag */ u"PySide6.QtCore.Qt.WindowType"_s, + // This is found in QtWidgets but should be in QtGui. + /* IntEnum */ u"PySide6.QtGui.QFileSystemModel.Roles"_s, + /* IntEnum */ u"PySide6.QtGui.QFont.Stretch"_s, + /* IntEnum */ u"PySide6.QtGui.QFont.Weight"_s, + /* IntEnum */ u"PySide6.QtGui.QTextDocument.ResourceType"_s, + /* IntEnum */ u"PySide6.QtGui.QTextFormat.FormatType"_s, + /* IntEnum */ u"PySide6.QtGui.QTextFormat.ObjectTypes"_s, + /* IntEnum */ u"PySide6.QtGui.QTextFormat.Property"_s, + /* IntEnum */ u"PySide6.QtWidgets.QDialog.DialogCode"_s, + /* IntEnum */ u"PySide6.QtWidgets.QFrame.Shadow"_s, + /* IntEnum */ u"PySide6.QtWidgets.QFrame.Shape"_s, + /* IntEnum */ u"PySide6.QtWidgets.QListWidgetItem.ItemType"_s, + /* IntFlag */ u"PySide6.QtWidgets.QMessageBox.StandardButton"_s, + // note: "QSizePolicy::PolicyFlag" is set as IntFlag without flags + /* IntFlag */ u"PySide6.QtWidgets.QSizePolicy.PolicyFlag"_s, + /* IntEnum */ u"PySide6.QtWidgets.QStyle.ComplexControl"_s, + /* IntEnum */ u"PySide6.QtWidgets.QStyle.ContentsType"_s, + /* IntEnum */ u"PySide6.QtWidgets.QStyle.ControlElement"_s, + /* IntEnum */ u"PySide6.QtWidgets.QStyle.PixelMetric"_s, + /* IntEnum */ u"PySide6.QtWidgets.QStyle.PrimitiveElement"_s, + /* IntEnum */ u"PySide6.QtWidgets.QStyle.StandardPixmap"_s, + /* IntEnum */ u"PySide6.QtWidgets.QStyle.StyleHint"_s, + /* IntEnum */ u"PySide6.QtWidgets.QStyle.SubElement"_s, + /* IntEnum */ u"PySide6.QtWidgets.QTableWidgetItem.ItemType"_s, + /* IntEnum */ u"PySide6.QtWidgets.QTreeWidgetItem.ItemType"_s, + /* IntEnum */ u"PySide6.QtCharts.QBoxSet.ValuePositions"_s, + /* IntEnum */ u"PySide6.QtMultimedia.QMediaPlayer.Loops"_s, + /* IntEnum */ u"PySide6.QtQuick.QSGGeometry.DrawingMode"_s, + /* IntEnum */ u"PySide6.QtWebEngineCore.QWebEngineScript.ScriptWorldId"_s, + // Added because it should really be used as number + /* IntEnum */ u"PySide6.QtCore.QMetaType.Type"_s, + /* IntEnum */ u"PySide6.QtSerialPort.QSerialPort.BaudRate"_s, + }; + return result; +} +#endif + +static bool _shouldInheritInt(const AbstractMetaEnum &cppEnum) +{ + return !cppEnum.fullName().startsWith(u"PySide6."_s); +} + +static QString BuildEnumFlagInfo(const AbstractMetaEnum &cppEnum) +{ + auto enumType = cppEnum.typeEntry(); + QString result = _plainName(enumType->name()); + auto flags = enumType->flags(); + auto decision = enumType->pythonEnumType(); + bool _int = _shouldInheritInt(cppEnum); + bool _flag = bool(flags); + + if (decision != TypeSystem::PythonEnumType::Unspecified) { + _int = decision == TypeSystem::PythonEnumType::IntEnum || + decision == TypeSystem::PythonEnumType::IntFlag; + _flag = decision == TypeSystem::PythonEnumType::Flag || + decision == TypeSystem::PythonEnumType::IntFlag; + } + result += _flag ? (_int ? u":IntFlag"_s : u":Flag"_s) + : (_int ? u":IntEnum"_s : u":Enum"_s); + if (flags) + result += u':' + _plainName(flags->flagsName()); + return u'"' + result + u'"'; +} + +static void writePyGetSetDefEntry(TextStream &s, const QString &name, + const QString &getFunc, const QString &setFunc) +{ + s << "{const_cast<char *>(\"" << mangleName(name) << "\"), " << getFunc << ", " + << (setFunc.isEmpty() ? NULL_PTR : setFunc) << ", nullptr, nullptr},\n"; +} + +static bool generateRichComparison(const GeneratorContext &c) +{ + const auto metaClass = c.metaClass(); + if (c.forSmartPointer()) { + auto te = std::static_pointer_cast<const SmartPointerTypeEntry>(metaClass->typeEntry()); + return te->smartPointerType() == TypeSystem::SmartPointerType::Shared; + } + + return !metaClass->isNamespace() && metaClass->hasComparisonOperatorOverload(); +} + +void CppGenerator::generateIncludes(TextStream &s, const GeneratorContext &classContext, + const IncludeGroupList &includes, + const AbstractMetaClassCList &innerClasses) const +{ + const auto metaClass = classContext.metaClass(); + + // write license comment + s << licenseComment() << '\n'; + + const bool normalClass = !classContext.forSmartPointer(); + // Normally only required for classes for which we want to generate protected API, + // but it needs to be generated into all files to ensure ODR for Unity builds. + if (!avoidProtectedHack()) + s << HeaderGenerator::protectedHackDefine; + + QByteArrayList cppIncludes{"typeinfo", "iterator", // for containers + "cctype", "cstring"}; + // headers + s << "// default includes\n"; + s << "#include <shiboken.h>\n"; + if (wrapperDiagnostics()) { + s << "#include <helper.h>\n"; + cppIncludes << "iostream"; + } + + if (normalClass && usePySideExtensions()) { + s << includeQDebug; + if (metaClass->hasToStringCapability()) + s << "#include <QtCore/QBuffer>\n"; + if (isQObject(metaClass)) { + s << "#include <pysideqobject.h>\n" + << "#include <pysidesignal.h>\n" + << "#include <pysideproperty.h>\n" + << "#include <signalmanager.h>\n" + << "#include <pysidemetafunction.h>\n"; + } + s << "#include <pysideqenum.h>\n" + << "#include <pysideqmetatype.h>\n" + << "#include <pysideutils.h>\n" + << "#include <feature_select.h>\n" + << "QT_WARNING_DISABLE_DEPRECATED\n\n"; + } + + // The multiple inheritance initialization function + // needs the 'set' class from C++ STL. + if (normalClass && getMultipleInheritingClass(metaClass) != nullptr) + cppIncludes << "algorithm" << "set"; + if (normalClass && metaClass->generateExceptionHandling()) + cppIncludes << "exception"; + + s << "\n// module include\n" << "#include \"" << getModuleHeaderFileName() << "\"\n"; + if (hasPrivateClasses()) + s << "#include \"" << getPrivateModuleHeaderFileName() << "\"\n"; + + s << "\n// main header\n" << "#include \"" + << HeaderGenerator::headerFileNameForContext(classContext) << "\"\n"; + + if (!innerClasses.isEmpty()) { + s << "\n// inner classes\n"; + for (const auto &innerClass : innerClasses) { + GeneratorContext innerClassContext = contextForClass(innerClass); + s << "#include \"" + << HeaderGenerator::headerFileNameForContext(innerClassContext) << "\"\n"; + } + } + + if (avoidProtectedHack()) + s << baseWrapperIncludes(classContext); + + for (const auto &g : includes) + s << g; + + // C++ includes + std::sort(cppIncludes.begin(), cppIncludes.end()); + s << '\n'; + for (const auto &i : std::as_const(cppIncludes)) + s << "#include <" << i << ">\n"; +} + +// Write methods definition +void CppGenerator::writePyMethodDefs(TextStream &s, const QString &className, + const QString &methodsDefinitions) +{ + s << "static PyMethodDef " << className << "_methods[] = {\n" << indent + << methodsDefinitions << METHOD_DEF_SENTINEL << outdent << "};\n\n"; +} + +void CppGenerator::writeModuleCodeSnips(TextStream &s, const CodeSnipList &codeSnips, + TypeSystem::CodeSnipPosition position, + TypeSystem::Language language) const +{ + if (!codeSnips.isEmpty()) { + try { + writeCodeSnips(s, codeSnips, position, language); + } catch (const std::exception &e) { + throw Exception(msgSnippetError("module source of "_L1 + moduleName(), e.what())); + } + } +} + +bool CppGenerator::hasHashFunction(const AbstractMetaClassCPtr &c) +{ + return !c->typeEntry()->hashFunction().isEmpty() + || c->hasHashFunction(); +} + +static bool needsTypeDiscoveryFunction(const AbstractMetaClassCPtr &c) +{ + return c->baseClass() != nullptr + && (c->isPolymorphic() || !c->typeEntry()->polymorphicIdValue().isEmpty()); +} + +static void writeAddedTypeSignatures(TextStream &s, const ComplexTypeEntryCPtr &te) +{ + for (const auto &e : te->addedPyMethodDefEntrys()) { + if (auto count = e.signatures.size()) { + for (qsizetype i = 0; i < count; ++i) { + if (count > 1) + s << i << ':'; + s << e.signatures.at(i) << '\n'; + } + } + } +} + +/// Function used to write the class generated binding code on the buffer +/// \param s the output buffer +/// \param classContext the pointer to metaclass information +void CppGenerator::generateClass(TextStream &s, const GeneratorContext &classContext) +{ + if (classContext.forSmartPointer()) { + generateSmartPointerClass(s, classContext); + return; + } + + s.setLanguage(TextStream::Language::Cpp); + AbstractMetaClassCPtr metaClass = classContext.metaClass(); + const auto typeEntry = metaClass->typeEntry(); + + auto innerClasses = metaClass->innerClasses(); + for (auto it = innerClasses.begin(); it != innerClasses.end(); ) { + auto innerTypeEntry = (*it)->typeEntry(); + if (shouldGenerate(innerTypeEntry) && !innerTypeEntry->isSmartPointer()) + ++it; + else + it = innerClasses.erase(it); + } + + AbstractMetaEnumList classEnums = metaClass->enums(); + metaClass->getEnumsFromInvisibleNamespacesToBeGenerated(&classEnums); + + IncludeGroupList includeGroups; + if (!classContext.useWrapper() || !avoidProtectedHack()) + includeGroups.append(classIncludes(metaClass)); + generateIncludes(s, classContext, includeGroups, innerClasses); + + if (typeEntry->typeFlags().testFlag(ComplexTypeEntry::Deprecated)) + s << "#Deprecated\n"; + + // Use class base namespace + { + AbstractMetaClassCPtr context = metaClass->enclosingClass(); + while (context) { + if (context->isNamespace() && !context->enclosingClass() + && std::static_pointer_cast<const NamespaceTypeEntry>(context->typeEntry())->generateUsing()) { + s << "\nusing namespace " << context->qualifiedCppName() << ";\n"; + break; + } + context = context->enclosingClass(); + } + } + + s << '\n'; + + // class inject-code native/beginning + if (!typeEntry->codeSnips().isEmpty()) { + writeClassCodeSnips(s, typeEntry->codeSnips(), + TypeSystem::CodeSnipPositionBeginning, TypeSystem::NativeCode, + classContext); + s << '\n'; + } + + // python conversion rules + if (typeEntry->isValue()) { + auto vte = std::static_pointer_cast<const ValueTypeEntry>(typeEntry); + if (vte->hasTargetConversionRule()) { + s << "// Python Conversion\n"; + s << vte->targetConversionRule() << '\n'; + } + } + + if (classContext.useWrapper()) { + s << "// Native ---------------------------------------------------------\n\n"; + + if (avoidProtectedHack() && usePySideExtensions()) { + s << "void " << classContext.wrapperName() << "::pysideInitQtMetaTypes()\n{\n" + << indent; + writeInitQtMetaTypeFunctionBody(s, classContext); + s << outdent << "}\n\n"; + } + + int maxOverrides = 0; + writeCacheResetNative(s, classContext); + for (const auto &func : metaClass->functions()) { + const auto generation = functionGeneration(func); + if (generation.testFlag(FunctionGenerationFlag::WrapperConstructor)) + writeConstructorNative(s, classContext, func); + else if (generation.testFlag(FunctionGenerationFlag::VirtualMethod)) + writeVirtualMethodNative(s, func, maxOverrides++); + } + + if (shouldGenerateMetaObjectFunctions(metaClass)) + writeMetaObjectMethod(s, classContext); + if (!avoidProtectedHack() || !metaClass->hasPrivateDestructor()) + writeDestructorNative(s, classContext); + } + + for (const auto &f : metaClass->userAddedPythonOverrides()) + writeUserAddedPythonOverride(s, f); + + StringStream smd(TextStream::Language::Cpp); + StringStream md(TextStream::Language::Cpp); + StringStream signatureStream(TextStream::Language::Cpp); + + s << openTargetExternC; + + const auto &functionGroups = getFunctionGroups(metaClass); + for (auto it = functionGroups.cbegin(), end = functionGroups.cend(); it != end; ++it) { + if (contains(sequenceProtocols(), it.key()) || contains(mappingProtocols(), it.key())) + continue; + const AbstractMetaFunctionCList &overloads = it.value(); + if (overloads.isEmpty()) + continue; + + const auto rfunc = overloads.constFirst(); + OverloadData overloadData(overloads, api()); + + if (rfunc->isConstructor()) { + writeConstructorWrapper(s, overloadData, classContext); + writeSignatureInfo(signatureStream, overloadData); + } + // call operators + else if (rfunc->name() == u"operator()") { + writeMethodWrapper(s, overloadData, classContext); + writeSignatureInfo(signatureStream, overloadData); + } + else if (!rfunc->isOperatorOverload()) { + writeMethodWrapper(s, overloadData, classContext); + writeSignatureInfo(signatureStream, overloadData); + // For a mixture of static and member function overloads, + // a separate PyMethodDef entry is written which is referenced + // in the PyMethodDef list and later in getattro() for handling + // the non-static case. + const auto defEntries = methodDefinitionEntries(overloadData); + if (OverloadData::hasStaticAndInstanceFunctions(overloads)) { + QString methDefName = cpythonMethodDefinitionName(rfunc); + smd << "static PyMethodDef " << methDefName << " = " << indent + << defEntries.constFirst() << outdent << ";\n\n"; + } + const auto &fname = rfunc->name(); + if (!m_tpFuncs.contains(fname) && !m_nbFuncs.contains(fname)) + md << defEntries; + } + } + for (const auto &pyMethodDef : typeEntry->addedPyMethodDefEntrys()) + md << pyMethodDef << ",\n"; + + if (typeEntry->isValue()) + writeCopyFunction(s, md, signatureStream, classContext); + + const QString methodsDefinitions = md.toString(); + const QString singleMethodDefinitions = smd.toString(); + + const QString className = chopType(cpythonTypeName(metaClass)); + + // Write single method definitions + s << singleMethodDefinitions; + + if (usePySideExtensions()) { + // PYSIDE-1019: Write a compressed list of all properties `name:getter[:setter]`. + // Default values are suppressed. + QStringList sorter; + for (const auto &spec : metaClass->propertySpecs()) { + if (!spec.generateGetSetDef()) + sorter.append(buildPropertyString(spec)); + } + sorter.sort(); + + s << '\n'; + s << "static const char *" << className << "_PropertyStrings[] = {\n" << indent; + for (const auto &entry : std::as_const(sorter)) + s << entry << ",\n"; + s << NULL_PTR << " // Sentinel\n" + << outdent << "};\n\n"; + + } + // PYSIDE-1735: Write an EnumFlagInfo structure + QStringList sorter; + for (const auto &entry : std::as_const(classEnums)) + sorter.append(BuildEnumFlagInfo(entry)); + sorter.sort(); + if (!sorter.empty()) { + s << "static const char *" << className << "_EnumFlagInfo[] = {\n" << indent; + for (const auto &entry : std::as_const(sorter)) + s << entry << ",\n"; + s << NULL_PTR << " // Sentinel\n" + << outdent << "};\n\n"; + } + + // Write methods definition + writePyMethodDefs(s, className, methodsDefinitions); + + // Write tp_s/getattro function + const AttroCheck attroCheck = checkAttroFunctionNeeds(metaClass); + if ((attroCheck & AttroCheckFlag::GetattroMask) != 0) + writeGetattroFunction(s, attroCheck, classContext); + if ((attroCheck & AttroCheckFlag::SetattroMask) != 0) + writeSetattroFunction(s, attroCheck, classContext); + + if (const auto f = boolCast(metaClass) ; f.has_value()) + writeNbBoolFunction(classContext, f.value(), s); + + if (supportsNumberProtocol(metaClass)) { + const auto numberProtocolOps = numberProtocolOperators(metaClass); + for (const auto &overloads : numberProtocolOps) { + OverloadData overloadData(overloads, api()); + writeMethodWrapper(s, overloadData, classContext); + writeSignatureInfo(signatureStream, overloadData); + } + } + + if (supportsSequenceProtocol(metaClass)) { + writeSequenceMethods(s, metaClass, classContext); + } + + if (supportsMappingProtocol(metaClass)) { + writeMappingMethods(s, metaClass, classContext); + } + + if (generateRichComparison(classContext)) { + s << "// Rich comparison\n"; + writeRichCompareFunction(s, classContext); + } + + if (shouldGenerateGetSetList(metaClass)) { + const AbstractMetaFieldList &fields = metaClass->fields(); + for (const AbstractMetaField &metaField : fields) { + if (metaField.canGenerateGetter()) + writeGetterFunction(s, metaField, classContext); + if (metaField.canGenerateSetter()) + writeSetterFunction(s, metaField, classContext); + s << '\n'; + } + + 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() << '\n'; + s << "static PyGetSetDef " << cpythonGettersSettersDefinitionName(metaClass) + << "[] = {\n" << indent; + for (const AbstractMetaField &metaField : fields) { + const bool canGenerateGetter = metaField.canGenerateGetter(); + const bool canGenerateSetter = metaField.canGenerateSetter(); + if (canGenerateGetter || canGenerateSetter) { + const QString getter = canGenerateGetter + ? cpythonGetterFunctionName(metaField) : QString(); + const QString setter = canGenerateSetter + ? cpythonSetterFunctionName(metaField) : QString(); + const auto names = metaField.definitionNames(); + for (const auto &name : names) + writePyGetSetDefEntry(s, name, getter, setter); + } + } + + for (const QPropertySpec &property : metaClass->propertySpecs()) { + if (property.generateGetSetDef() || !usePySideExtensions()) { + const QString setter = property.hasWrite() + ? cpythonSetterFunctionName(property, metaClass) : QString(); + writePyGetSetDefEntry(s, property.name(), + cpythonGetterFunctionName(property, metaClass), setter); + } + } + s << "{nullptr, nullptr, nullptr, nullptr, nullptr} // Sentinel\n" + << outdent << "};\n\n"; + } + + s << closeExternC; + + if (hasHashFunction(metaClass)) + writeHashFunction(s, classContext); + + // Write tp_traverse and tp_clear functions. + writeTpTraverseFunction(s, metaClass); + writeTpClearFunction(s, metaClass); + + writeClassDefinition(s, metaClass, classContext); + s << '\n'; + + if (needsTypeDiscoveryFunction(metaClass)) { + writeTypeDiscoveryFunction(s, metaClass); + s << '\n'; + } + + writeConverterFunctions(s, metaClass, classContext); + writeAddedTypeSignatures(signatureStream, typeEntry); + writeClassRegister(s, metaClass, classContext, signatureStream); + + if (metaClass->hasStaticFields()) + writeStaticFieldInitialization(s, metaClass); + + // class inject-code native/end + if (!typeEntry->codeSnips().isEmpty()) { + writeClassCodeSnips(s, typeEntry->codeSnips(), + TypeSystem::CodeSnipPositionEnd, TypeSystem::NativeCode, + classContext); + s << '\n'; + } +} + +void CppGenerator::writeMethodWrapper(TextStream &s, TextStream &definitionStream, + TextStream &signatureStream, + const AbstractMetaFunctionCList &overloads, + const GeneratorContext &classContext) const +{ + OverloadData overloadData(overloads, api()); + writeMethodWrapper(s, overloadData, classContext); + writeSignatureInfo(signatureStream, overloadData); + definitionStream << methodDefinitionEntries(overloadData); +} + +void CppGenerator::writeCacheResetNative(TextStream &s, const GeneratorContext &classContext) +{ + s << "void " << classContext.wrapperName() + << "::resetPyMethodCache()\n{\n" << indent + << "std::fill_n(m_PyMethodCache, sizeof(m_PyMethodCache) / sizeof(m_PyMethodCache[0]), false);\n" + << outdent << "}\n\n"; +} + +void CppGenerator::writeConstructorNative(TextStream &s, const GeneratorContext &classContext, + const AbstractMetaFunctionCPtr &func) const +{ + const QString qualifiedName = classContext.wrapperName() + u"::"_s; + s << functionSignature(func, qualifiedName, QString(), + OriginalTypeDescription | SkipDefaultValues); + s << " : "; + writeFunctionCall(s, func); + s << "\n{\n" << indent; + if (wrapperDiagnostics()) + s << R"(std::cerr << __FUNCTION__ << ' ' << this << '\n';)" << '\n'; + const AbstractMetaArgument *lastArg = func->arguments().isEmpty() ? nullptr : &func->arguments().constLast(); + s << "resetPyMethodCache();\n"; + writeCodeSnips(s, func->injectedCodeSnips(), TypeSystem::CodeSnipPositionBeginning, + TypeSystem::NativeCode, func, false, lastArg); + s << "// ... middle\n"; + writeCodeSnips(s, func->injectedCodeSnips(), TypeSystem::CodeSnipPositionEnd, + TypeSystem::NativeCode, func, false, lastArg); + s << outdent << "}\n\n"; +} + +void CppGenerator::writeDestructorNative(TextStream &s, + const GeneratorContext &classContext) +{ + s << classContext.wrapperName() << "::~" + << classContext.wrapperName() << "()\n{\n" << indent; + if (wrapperDiagnostics()) + s << R"(std::cerr << __FUNCTION__ << ' ' << this << '\n';)" << '\n'; + // kill pyobject + s << R"(SbkObject *wrapper = Shiboken::BindingManager::instance().retrieveWrapper(this); +Shiboken::Object::destroy(wrapper, this); +)" << outdent << "}\n"; +} + +// Return type for error messages when getting invalid types from virtual +// methods implemented in Python in C++ wrappers +QString CppGenerator::getVirtualFunctionReturnTypeName(const AbstractMetaFunctionCPtr &func) const +{ + if (func->type().isVoid()) + return u"\"\""_s; + + if (func->isTypeModified()) + return u'"' + func->modifiedTypeName() + u'"'; + + // SbkType would return null when the type is a container. + auto typeEntry = func->type().typeEntry(); + if (typeEntry->isContainer()) { + const auto cte = std::static_pointer_cast<const ContainerTypeEntry>(typeEntry); + switch (cte->containerKind()) { + case ContainerTypeEntry::ListContainer: + case ContainerTypeEntry::SpanContainer: + break; + case ContainerTypeEntry::SetContainer: + return uR"("set")"_s; + break; + case ContainerTypeEntry::MapContainer: + case ContainerTypeEntry::MultiMapContainer: + return uR"("dict")"_s; + break; + case ContainerTypeEntry::PairContainer: + return uR"("tuple")"_s; + break; + } + return uR"("list")"_s; + } + if (typeEntry->isSmartPointer()) + return u'"' + typeEntry->qualifiedCppName() + u'"'; + + if (avoidProtectedHack()) { + auto metaEnum = api().findAbstractMetaEnum(func->type().typeEntry()); + if (metaEnum.has_value() && metaEnum->isProtected()) + return u'"' + protectedEnumSurrogateName(metaEnum.value()) + u'"'; + } + + if (func->type().isPrimitive()) + return u'"' + func->type().name() + u'"'; + + return u"Shiboken::SbkType< "_s + + typeEntry->qualifiedCppName() + u" >()->tp_name"_s; +} + +// When writing an overridden method of a wrapper class, write the part +// calling the C++ function in case no overload in Python exists. +void CppGenerator::writeVirtualMethodCppCall(TextStream &s, + const AbstractMetaFunctionCPtr &func, + const QString &funcName, + const CodeSnipList &snips, + const AbstractMetaArgument *lastArg, + const TypeEntryCPtr &retType, + const QString &returnStatement, bool hasGil) const +{ + if (!snips.isEmpty()) { + writeCodeSnips(s, snips, TypeSystem::CodeSnipPositionBeginning, + TypeSystem::ShellCode, func, false, lastArg); + } + + if (func->isAbstract()) { + if (!hasGil) + s << "Shiboken::GilState gil;\n"; + s << "Shiboken::Errors::setPureVirtualMethodError(\"" + << func->ownerClass()->name() << '.' << funcName << "\");\n" + << returnStatement << '\n'; + return; + } + + if (hasGil) + s << "gil.release();\n"; + + if (retType) + s << "return "; + s << "this->::" << func->implementingClass()->qualifiedCppName() << "::"; + writeFunctionCall(s, func, Generator::VirtualCall); + s << ";\n"; + if (retType) + return; + if (!snips.isEmpty()) { + writeCodeSnips(s, snips, TypeSystem::CodeSnipPositionEnd, + TypeSystem::ShellCode, func, false, lastArg); + } + s << "return;\n"; +} + +// Determine the return statement (void or a result value). + +CppGenerator::VirtualMethodReturn + CppGenerator::virtualMethodReturn(const ApiExtractorResult &api, + const AbstractMetaFunctionCPtr &func, + const FunctionModificationList &functionModifications) +{ + VirtualMethodReturn result; + if (func->isVoid()) { + result.statement = "return;"_L1; + return result; + } + + result.statement = "return "_L1; + const AbstractMetaType &returnType = func->type(); + for (const FunctionModification &mod : functionModifications) { + for (const ArgumentModification &argMod : mod.argument_mods()) { + if (argMod.index() == 0 && !argMod.replacedDefaultExpression().isEmpty()) { + static const QRegularExpression regex("%(\\d+)"_L1); + Q_ASSERT(regex.isValid()); + QString expr = argMod.replacedDefaultExpression(); + for (int offset = 0; ; ) { + const QRegularExpressionMatch match = regex.match(expr, offset); + if (!match.hasMatch()) + break; + const int argId = match.capturedView(1).toInt() - 1; + if (argId < 0 || argId > func->arguments().size()) { + qCWarning(lcShiboken, "The expression used in return value contains an invalid index."); + break; + } + expr.replace(match.captured(0), func->arguments().at(argId).name()); + offset = match.capturedStart(1); + } + DefaultValue defaultReturnExpr(DefaultValue::Custom, expr); + result.statement += defaultReturnExpr.returnValue() + u';'; + return result; + } + } + } + QString errorMessage; + const auto defaultReturnExpr = minimalConstructor(api, returnType, &errorMessage); + if (!defaultReturnExpr.has_value()) { + QString errorMsg = QLatin1StringView(__FUNCTION__) + u": "_s + + func->classQualifiedSignature(); + errorMsg = msgCouldNotFindMinimalConstructor(errorMsg, + func->type().cppSignature(), + errorMessage); + throw Exception(errorMsg); + } + + result.needsReference = returnType.referenceType() == LValueReference; + result.statement += (result.needsReference + ? virtualMethodStaticReturnVar : defaultReturnExpr->returnValue()) + u';'; + return result; +} + +// Create an argument for Py_BuildValue() when writing virtual methods. +// Return a pair of (argument, format-char). +std::pair<QString, QChar> CppGenerator::virtualMethodNativeArg(const AbstractMetaFunctionCPtr &func, + const AbstractMetaArgument &arg) +{ + if (func->hasConversionRule(TypeSystem::TargetLangCode, arg.argumentIndex() + 1)) + return {arg.name() + CONV_RULE_OUT_VAR_SUFFIX, u'N'}; + + const auto &type = arg.type(); + auto argTypeEntry = type.typeEntry(); + // Check for primitive types convertible by Py_BuildValue() + if (argTypeEntry->isPrimitive() && !type.isCString()) { + const auto pte = basicReferencedTypeEntry(argTypeEntry); + auto it = formatUnits().constFind(pte->name()); + if (it != formatUnits().constEnd()) + return {arg.name(), it.value()}; + } + + // Rest: convert + StringStream ac(TextStream::Language::Cpp); + writeToPythonConversion(ac, type, func->ownerClass(), arg.name()); + return {ac.toString(), u'N'}; +} + +static const char PYTHON_ARGS_ARRAY[] = "pyArgArray"; + +void CppGenerator::writeVirtualMethodNativeVectorCallArgs(TextStream &s, + const AbstractMetaFunctionCPtr &func, + const AbstractMetaArgumentList &arguments, + const QList<int> &invalidateArgs) +{ + Q_ASSERT(!arguments.isEmpty()); + s << "PyObject *" << PYTHON_ARGS_ARRAY <<'[' << arguments.size() << "] = {\n" << indent; + const qsizetype last = arguments.size() - 1; + for (qsizetype i = 0; i <= last; ++i) { + const AbstractMetaArgument &arg = arguments.at(i); + if (func->hasConversionRule(TypeSystem::TargetLangCode, arg.argumentIndex() + 1)) { + s << arg.name() + CONV_RULE_OUT_VAR_SUFFIX; + } else { + writeToPythonConversion(s, arg.type(), func->ownerClass(), arg.name()); + } + if (i < last) + s << ','; + s << '\n'; + } + s << outdent << "};\n"; + + if (!invalidateArgs.isEmpty()) + s << '\n'; + for (int index : invalidateArgs) { + s << "const bool invalidateArg" << index << " = Py_REFCNT(" << PYTHON_ARGS_ARRAY << + '[' << index - 1 << "]) == 1;\n"; + } +} + +void CppGenerator::writeVirtualMethodNativeArgs(TextStream &s, + const AbstractMetaFunctionCPtr &func, + const AbstractMetaArgumentList &arguments, + const QList<int> &invalidateArgs) +{ + s << "Shiboken::AutoDecRef " << PYTHON_ARGS << '('; + if (arguments.isEmpty()) { + s << "PyTuple_New(0));\n"; + return; + } + + QString format; + QStringList argConversions; + for (const AbstractMetaArgument &arg : arguments) { + auto argPair = virtualMethodNativeArg(func, arg); + argConversions.append(argPair.first); + format += argPair.second; + } + + s << "Py_BuildValue(\"(" << format << ")\",\n" + << indent << argConversions.join(u",\n"_s) << outdent << "\n));\n"; + + for (int index : std::as_const(invalidateArgs)) { + s << "bool invalidateArg" << index << " = Py_REFCNT(PyTuple_GET_ITEM(" << PYTHON_ARGS + << ", " << index - 1 << ")) == 1;\n"; + } +} + +static bool isArgumentNotRemoved(const AbstractMetaArgument &a) +{ + return !a.isModifiedRemoved(); +} + +// PyObject_Vectorcall(): since 3.9 +static const char vectorCallCondition[] = + "#if !defined(PYPY_VERSION) && !defined(Py_LIMITED_API)\n"; + +// PyObject_CallNoArgs(): since 3.9, stable API since 3.10 +static const char noArgsCallCondition[] = + "#if !defined(PYPY_VERSION) && ((defined(Py_LIMITED_API) && Py_LIMITED_API >= 0x030A0000) || !defined(Py_LIMITED_API))\n"; +static const char inverseNoArgsCallCondition[] = + "#if defined(PYPY_VERSION) || (defined(Py_LIMITED_API) && Py_LIMITED_API < 0x030A0000)\n"; + +static inline void writeVirtualMethodStaticReturnVar(TextStream &s, const AbstractMetaFunctionCPtr &func) +{ + s << "static " << func->type().typeEntry()->qualifiedCppName() << ' ' + << virtualMethodStaticReturnVar << ";\n"; +} + +static void writeFuncNameVar(TextStream &s, const AbstractMetaFunctionCPtr &func, + const QString &funcName) +{ + // PYSIDE-1019: Add info about properties + int propFlag = 0; + if (func->isPropertyReader()) + propFlag |= 1; + if (func->isPropertyWriter()) + propFlag |= 2; + if (propFlag && func->isStatic()) + propFlag |= 4; + QString propStr; + if (propFlag != 90) + propStr = QString::number(propFlag) + u':'; + + if (propFlag != 0) + s << "// This method belongs to a property.\n"; + s << "static const char *funcName = \""; + if (propFlag != 0) + s << propFlag << ':'; + s << funcName << "\";\n"; +} + +void CppGenerator::writeVirtualMethodNative(TextStream &s, + const AbstractMetaFunctionCPtr &func, + int cacheIndex) const +{ + TypeEntryCPtr retType = func->type().typeEntry(); + const QString funcName = func->isOperatorOverload() + ? pythonOperatorFunctionName(func) : func->definitionNames().constFirst(); + + QString prefix = wrapperName(func->ownerClass()) + u"::"_s; + s << functionSignature(func, prefix, QString(), Generator::SkipDefaultValues | + Generator::OriginalTypeDescription) + << "\n{\n" << indent; + + const auto returnStatement = virtualMethodReturn(api(), func, + func->modifications()); + + if (returnStatement.needsReference) + writeVirtualMethodStaticReturnVar(s, func); + + const bool isAbstract = func->isAbstract(); + if (isAbstract && func->isModifiedRemoved()) { + qCWarning(lcShiboken, "%s", qPrintable(msgPureVirtualFunctionRemoved(func.get()))); + s << returnStatement.statement << '\n' << outdent << "}\n\n"; + return; + } + + const CodeSnipList snips = func->hasInjectedCode() + ? func->injectedCodeSnips() : CodeSnipList(); + const AbstractMetaArgument *lastArg = func->arguments().isEmpty() + ? nullptr : &func->arguments().constLast(); + + // Write declaration/native injected code. + if (!snips.isEmpty()) { + writeCodeSnips(s, snips, TypeSystem::CodeSnipPositionDeclaration, + TypeSystem::ShellCode, func, false, lastArg); + } + + if (wrapperDiagnostics()) { + s << "std::cerr << "; +#ifndef Q_CC_MSVC // g++ outputs __FUNCTION__ unqualified + s << '"' << prefix << R"(" << )"; +#endif + s << R"(__FUNCTION__ << ' ' << this << " m_PyMethodCache[" << )" + << cacheIndex << R"( << "]=" << m_PyMethodCache[)" << cacheIndex + << R"(] << '\n';)" << '\n'; + } + // PYSIDE-803: Build a boolean cache for unused overrides + const bool multi_line = func->isVoid() || !snips.isEmpty() || isAbstract; + s << "if (m_PyMethodCache[" << cacheIndex << "])" << (multi_line ? " {\n" : "\n") + << indent; + writeVirtualMethodCppCall(s, func, funcName, snips, lastArg, retType, + returnStatement.statement, false); + s << outdent; + if (multi_line) + s << "}\n"; + + s << "Shiboken::GilState gil;\n"; + + // Get out of virtual method call if someone already threw an error. + s << "if (" << shibokenErrorsOccurred << ")\n" << indent + << returnStatement.statement << '\n' << outdent; + + s << "static PyObject *nameCache[2] = {};\n"; + writeFuncNameVar(s, func, funcName); + s << "Shiboken::AutoDecRef " << PYTHON_OVERRIDE_VAR + << "(Shiboken::BindingManager::instance().getOverride(this, nameCache, funcName));\n" + << "if (" << PYTHON_OVERRIDE_VAR << ".isNull()) {\n" << indent; + if (useOverrideCaching(func->ownerClass())) + s << "m_PyMethodCache[" << cacheIndex << "] = true;\n"; + writeVirtualMethodCppCall(s, func, funcName, snips, lastArg, retType, + returnStatement.statement, true); + s << outdent << "}\n\n"; //WS + + if (!snips.isEmpty()) { + writeCodeSnips(s, snips, TypeSystem::CodeSnipPositionPyOverride, + TypeSystem::ShellCode, func, false, lastArg); + } + + writeVirtualMethodPythonOverride(s, func, snips, returnStatement); +} + +void CppGenerator::writeVirtualMethodPythonOverride(TextStream &s, + const AbstractMetaFunctionCPtr &func, + const CodeSnipList &snips, + const VirtualMethodReturn &returnStatement) const +{ + writeConversionRule(s, func, TypeSystem::TargetLangCode, false); + + bool invalidateReturn = false; + QList<int> invalidateArgs; + for (const FunctionModification &funcMod : func->modifications()) { + for (const ArgumentModification &argMod : funcMod.argument_mods()) { + const int index = argMod.index(); + if (index == 0) { + if (argMod.targetOwnerShip() == TypeSystem::CppOwnership) + invalidateReturn = true; + } else { + const int actualIndex = func->actualArgumentIndex(index - 1) + 1; + if (argMod.resetAfterUse() && !invalidateArgs.contains(actualIndex)) + invalidateArgs.append(actualIndex); + } + } + } + std::sort(invalidateArgs.begin(), invalidateArgs.end()); + + auto arguments = func->arguments(); + auto removedEnd = std::stable_partition(arguments.begin(), arguments.end(), + isArgumentNotRemoved); + if (func->isAbstract()) { // Base function is not called, indicate unused arguments. + for (auto it = removedEnd; it != arguments.end(); ++it) + s << sbkUnusedVariableCast(it->name()); + } + arguments.erase(removedEnd, arguments.end()); + + // FIXME PYSIDE-7: new functions PyObject_Vectorcall() (since 3.9) and + // PyObject_CallNoArgs() (since 3.9, stable API since 3.10) might have + // become part of the stable API? + + // Code snips might expect the args tuple, don't generate new code + const bool generateNewCall = snips.isEmpty(); + const qsizetype argCount = arguments.size(); + const char *newCallCondition = argCount == 0 ? noArgsCallCondition : vectorCallCondition; + if (generateNewCall) { + if (argCount > 0) { + s << newCallCondition; + writeVirtualMethodNativeVectorCallArgs(s, func, arguments, invalidateArgs); + s << "#else\n"; + } else { + s << inverseNoArgsCallCondition; + } + } + writeVirtualMethodNativeArgs(s, func, arguments, invalidateArgs); + if (generateNewCall) + s << "#endif\n"; + s << '\n'; + + if (!snips.isEmpty()) { + if (func->injectedCodeUsesPySelf()) + s << "PyObject *pySelf = BindingManager::instance().retrieveWrapper(this);\n"; + + const AbstractMetaArgument *lastArg = func->arguments().isEmpty() + ? nullptr : &func->arguments().constLast(); + writeCodeSnips(s, snips, TypeSystem::CodeSnipPositionBeginning, + TypeSystem::NativeCode, func, false, lastArg); + } + + qsizetype returnIndirections = 0; + + if (!func->injectedCodeCallsPythonOverride()) { + if (generateNewCall) { + s << newCallCondition << "Shiboken::AutoDecRef " << PYTHON_RETURN_VAR << '('; + if (argCount > 0) { + s << "PyObject_Vectorcall(" << PYTHON_OVERRIDE_VAR << ", " + << PYTHON_ARGS_ARRAY << ", " << argCount << ", nullptr));\n"; + for (int argIndex : std::as_const(invalidateArgs)) { + s << "if (invalidateArg" << argIndex << ")\n" << indent + << "Shiboken::Object::invalidate(" << PYTHON_ARGS_ARRAY + << '[' << (argIndex - 1) << "]);\n" << outdent; + } + for (qsizetype i = 0, size = arguments.size(); i < size; ++i) + s << "Py_DECREF(" << PYTHON_ARGS_ARRAY << '[' << i << "]);\n"; + } else { + s << "PyObject_CallNoArgs(" << PYTHON_OVERRIDE_VAR << "));\n"; + } + s << "#else\n"; + } + s << "Shiboken::AutoDecRef " << PYTHON_RETURN_VAR << "(PyObject_Call(" + << PYTHON_OVERRIDE_VAR << ", " << PYTHON_ARGS << ", nullptr));\n"; + + for (int argIndex : std::as_const(invalidateArgs)) { + s << "if (invalidateArg" << argIndex << ")\n" << indent + << "Shiboken::Object::invalidate(PyTuple_GET_ITEM(" << PYTHON_ARGS + << ", " << (argIndex - 1) << "));\n" << outdent; + } + if (generateNewCall) + s << "#endif\n"; + + s << "if (" << PYTHON_RETURN_VAR << ".isNull()) {\n" << indent + << "// An error happened in python code!\n" + << "Shiboken::Errors::storePythonOverrideErrorOrPrint(\"" + << func->ownerClass()->name() << "\", funcName);\n" + << returnStatement.statement << "\n" << outdent + << "}\n"; + + if (invalidateReturn) { + s << "bool invalidateArg0 = Py_REFCNT(" << PYTHON_RETURN_VAR << ") == 1;\n" + << "if (invalidateArg0)\n" << indent + << "Shiboken::Object::releaseOwnership(" << PYTHON_RETURN_VAR + << ".object());\n" << outdent; + } + + if (!func->isVoid()) { + + if (func->modifiedTypeName() != cPyObjectT) { + + s << "// Check return type\n"; + + if (!func->isTypeModified()) { + + s << PYTHON_TO_CPPCONVERSION_STRUCT << ' ' + << PYTHON_TO_CPP_VAR << " =\n" << indent + << cpythonIsConvertibleFunction(func->type()) + << PYTHON_RETURN_VAR << ");\n" << outdent + << "if (!" << PYTHON_TO_CPP_VAR << ") {\n" << indent + << "Shiboken::Warnings::warnInvalidReturnValue(\"" + << func->ownerClass()->name() << "\", funcName, " + << getVirtualFunctionReturnTypeName(func) << ", " + << "Py_TYPE(" << PYTHON_RETURN_VAR << ")->tp_name);\n" + << returnStatement.statement << '\n' << outdent + << "}\n"; + + } else { + + s << "bool typeIsValid = "; + if (func->isTypeModified()) { + writeTypeCheck(s, func->modifiedTypeName(), PYTHON_RETURN_VAR); + } else { + const bool numberType = isNumber(func->type().typeEntry()); + writeTypeCheck(s, func->type(), PYTHON_RETURN_VAR, numberType); + } + + s << ";\n"; + s << "if (!typeIsValid"; + if (func->type().isPointerToWrapperType()) + s << " && " << PYTHON_RETURN_VAR << " != Py_None"; + s << ") {\n" << indent + << "Shiboken::Warnings::warnInvalidReturnValue(\"" + << func->ownerClass()->name() << "\", funcName, " + << getVirtualFunctionReturnTypeName(func) << ", " + << "Py_TYPE(" << PYTHON_RETURN_VAR << ")->tp_name);\n" + << returnStatement.statement << '\n' << outdent + << "}\n"; + + } + } + + if (func->hasConversionRule(TypeSystem::NativeCode, 0)) { + writeConversionRule(s, func, TypeSystem::NativeCode, CPP_RETURN_VAR); + } else if (!func->injectedCodeHasReturnValueAttribution(TypeSystem::NativeCode)) { + returnIndirections = writePythonToCppTypeConversion( + s, func->type(), PYTHON_RETURN_VAR, + CPP_RETURN_VAR, func->implementingClass(), {}); + } + } + } + + for (const FunctionModification &funcMod : func->modifications()) { + for (const ArgumentModification &argMod : funcMod.argument_mods()) { + if (argMod.index() == 0 + && argMod.nativeOwnership() == TypeSystem::CppOwnership) { + s << "if (Shiboken::Object::checkType(" << PYTHON_RETURN_VAR << "))\n" << indent + << "Shiboken::Object::releaseOwnership(" << PYTHON_RETURN_VAR << ");\n" + << outdent; + } + } + } + + if (func->hasInjectedCode()) { + s << '\n'; + const AbstractMetaArgument *lastArg = func->arguments().isEmpty() + ? nullptr : &func->arguments().constLast(); + writeCodeSnips(s, snips, TypeSystem::CodeSnipPositionEnd, + TypeSystem::NativeCode, func, false, lastArg); + } + + if (!func->isVoid()) { + s << "return "; + TypeEntryCPtr retType = func->type().typeEntry(); + if (avoidProtectedHack() && retType->isEnum()) { + auto metaEnum = api().findAbstractMetaEnum(retType); + bool isProtectedEnum = metaEnum.has_value() && metaEnum->isProtected(); + if (isProtectedEnum) { + QString typeCast; + if (metaEnum->enclosingClass()) + typeCast += getFullTypeName(metaEnum->enclosingClass()); + typeCast += u"::"_s + metaEnum->name(); + s << '(' << typeCast << ')'; + } + } + + if (returnIndirections > 0) + s << QByteArray(returnIndirections, '*'); + s << CPP_RETURN_VAR << ";\n"; + } + + s << outdent << "}\n\n"; +} + +void CppGenerator::writeUserAddedPythonOverride(TextStream &s, + const AbstractMetaFunctionCPtr &func) const +{ + TypeEntryCPtr retType = func->type().typeEntry(); + const QString funcName = func->isOperatorOverload() + ? pythonOperatorFunctionName(func) : func->definitionNames().constFirst(); + + const CodeSnipList snips = func->hasInjectedCode() + ? func->injectedCodeSnips() : CodeSnipList(); + + QString prefix = wrapperName(func->ownerClass()) + u"::"_s; + s << '\n' << functionSignature(func, prefix, QString(), Generator::SkipDefaultValues | + Generator::OriginalTypeDescription) + << "\n{\n" << indent << sbkUnusedVariableCast("gil"); + + writeFuncNameVar(s, func, funcName); + + const auto returnStatement = virtualMethodReturn(api(), func, + func->modifications()); + writeVirtualMethodPythonOverride(s, func, snips, returnStatement); +} + +void CppGenerator::writeMetaObjectMethod(TextStream &s, + const GeneratorContext &classContext) const +{ + + const QString wrapperClassName = classContext.wrapperName(); + const QString qualifiedCppName = classContext.metaClass()->qualifiedCppName(); + s << "const QMetaObject *" << wrapperClassName << "::metaObject() const\n{\n"; + s << indent << "if (QObject::d_ptr->metaObject != nullptr)\n" + << indent << "return QObject::d_ptr->dynamicMetaObject();\n" << outdent + << "SbkObject *pySelf = Shiboken::BindingManager::instance().retrieveWrapper(this);\n" + << "if (pySelf == nullptr)\n" + << indent << "return " << qualifiedCppName << "::metaObject();\n" << outdent + << "return PySide::SignalManager::retrieveMetaObject(" + "reinterpret_cast<PyObject *>(pySelf));\n" + << outdent << "}\n\n"; + + // qt_metacall function + s << "int " << wrapperClassName + << "::qt_metacall(QMetaObject::Call call, int id, void **args)\n"; + s << "{\n" << indent; + + const auto list = classContext.metaClass()->queryFunctionsByName(u"qt_metacall"_s); + + CodeSnipList snips; + if (list.size() == 1) { + const auto &func = list.constFirst(); + snips = func->injectedCodeSnips(); + if (func->isUserAdded()) { + CodeSnipList snips = func->injectedCodeSnips(); + const bool usePyArgs = pythonFunctionWrapperUsesListOfArguments(func); + writeCodeSnips(s, snips, TypeSystem::CodeSnipPositionAny, + TypeSystem::NativeCode, func, usePyArgs, nullptr); + } + } + + s << "int result = " << qualifiedCppName << "::qt_metacall(call, id, args);\n" + << "return result < 0 ? result : PySide::SignalManager::qt_metacall(" + "this, call, id, args);\n" + << outdent << "}\n\n"; + + // qt_metacast function + writeMetaCast(s, classContext); +} + +void CppGenerator::writeMetaCast(TextStream &s, + const GeneratorContext &classContext) +{ + const QString wrapperClassName = classContext.wrapperName(); + const QString qualifiedCppName = classContext.metaClass()->qualifiedCppName(); + s << "void *" << wrapperClassName << "::qt_metacast(const char *_clname)\n{\n" + << indent << "if (_clname == nullptr)\n" << indent << "return {};\n" << outdent + << "SbkObject *pySelf = Shiboken::BindingManager::instance().retrieveWrapper(this);\n" + << "if (pySelf != nullptr && PySide::inherits(Py_TYPE(pySelf), _clname))\n" + << indent << "return static_cast<void *>(const_cast< " + << wrapperClassName << " *>(this));\n" << outdent + << "return " << qualifiedCppName << "::qt_metacast(_clname);\n" + << outdent << "}\n\n"; +} + +static void generateDeprecatedValueWarnings(TextStream &c, + const AbstractMetaEnum &metaEnum, + bool useSurrogateName) +{ + EnumTypeEntryCPtr enumType = metaEnum.typeEntry(); + const QString prefix = enumType->qualifiedCppName() + u"::"_s; + c << "switch (value) {\n"; + const auto &deprecatedValues = metaEnum.deprecatedValues(); + for (const auto &v : deprecatedValues) { + c << "case "; + if (useSurrogateName) + c << v.value().toString(); // Protected, use int representation + else + c << prefix << v.name(); + c << ":\n" << indent + << "Shiboken::Warnings::warnDeprecatedEnumValue(\"" << enumType->name() + << "\", \"" << v.name() << "\");\nbreak;\n" << outdent; + } + if (deprecatedValues.size() < metaEnum.values().size()) + c << "default:\n" << indent << "break;\n" << outdent; + c << "}\n"; +} + +void CppGenerator::writeEnumConverterFunctions(TextStream &s, const AbstractMetaEnum &metaEnum) const +{ + if (metaEnum.isPrivate() || metaEnum.isAnonymous()) + return; + EnumTypeEntryCPtr enumType = metaEnum.typeEntry(); + Q_ASSERT(enumType); + QString typeName = fixedCppTypeName(enumType); + QString enumPythonType = cpythonTypeNameExt(enumType); + const bool useSurrogateName = avoidProtectedHack() && metaEnum.isProtected(); + QString cppTypeName = useSurrogateName + ? protectedEnumSurrogateName(metaEnum) : getFullTypeName(enumType).trimmed(); + + StringStream c(TextStream::Language::Cpp); + if (metaEnum.isDeprecated()) + c << "Shiboken::Warnings::warnDeprecatedEnum(\"" << enumType->name() << "\");\n"; + + c << "const auto value = static_cast<" << cppTypeName + << ">(Shiboken::Enum::getValue(pyIn));\n"; + + // Warn about deprecated values unless it is protected+scoped (inaccessible values) + const bool valuesAcccessible = !useSurrogateName || metaEnum.enumKind() != EnumClass; + if (valuesAcccessible && metaEnum.hasDeprecatedValues()) + generateDeprecatedValueWarnings(c, metaEnum, useSurrogateName); + + c << "*reinterpret_cast<" << cppTypeName << " *>(cppOut) = value;\n"; + + ConfigurableScope configScope(s, enumType); + writePythonToCppFunction(s, c.toString(), typeName, typeName); + + QString pyTypeCheck = u"PyObject_TypeCheck(pyIn, "_s + enumPythonType + u')'; + writeIsPythonConvertibleToCppFunction(s, typeName, typeName, pyTypeCheck); + + c.clear(); + + c << "const int castCppIn = int(*reinterpret_cast<const " + << cppTypeName << " *>(cppIn));\n" << "return " + << "Shiboken::Enum::newItem(" << enumPythonType << ", castCppIn);\n"; + writeCppToPythonFunction(s, c.toString(), typeName, typeName); + s << '\n'; +} + +static void writePointerToPythonConverter(TextStream &c, + const AbstractMetaClassCPtr &metaClass, + const QString &typeName, + const QString &cpythonType) +{ + c << "auto *pyOut = reinterpret_cast<PyObject *>(Shiboken::BindingManager::instance().retrieveWrapper(cppIn));\n" + << "if (pyOut) {\n" << indent + << "Py_INCREF(pyOut);\nreturn pyOut;\n" << outdent + << "}\n"; + + const QString nameFunc = metaClass->typeEntry()->polymorphicNameFunction(); + if (nameFunc.isEmpty() && !metaClass->hasVirtualDestructor()) { + c << "return Shiboken::Object::newObjectWithHeuristics(" + << cpythonType << ", const_cast<void *>(cppIn), false);\n"; + return; + } + + c << "auto *tCppIn = reinterpret_cast<const " << typeName << R"( *>(cppIn); +const char *typeName = )"; + if (nameFunc.isEmpty()) + c << "typeid(*tCppIn).name();\n"; + else + c << nameFunc << "(tCppIn);\n"; + c << "return Shiboken::Object::newObjectForPointer(" + << cpythonType << ", const_cast<void *>(cppIn), false, typeName);\n"; +} + +void CppGenerator::writeConverterFunctions(TextStream &s, const AbstractMetaClassCPtr &metaClass, + const GeneratorContext &classContext) const +{ + s << "// Type conversion functions.\n\n"; + + AbstractMetaEnumList classEnums = metaClass->enums(); + auto typeEntry = metaClass->typeEntry(); + metaClass->getEnumsFromInvisibleNamespacesToBeGenerated(&classEnums); + if (!classEnums.isEmpty()) + s << "// Python to C++ enum conversion.\n"; + for (const AbstractMetaEnum &metaEnum : std::as_const(classEnums)) + writeEnumConverterFunctions(s, metaEnum); + + if (metaClass->isNamespace()) + return; + + QString typeName; + if (!classContext.forSmartPointer()) + typeName = getFullTypeName(metaClass); + else + typeName = getFullTypeName(classContext.preciseType()); + + QString cpythonType = cpythonTypeName(metaClass); + + // Returns the C++ pointer of the Python wrapper. + s << "// Python to C++ pointer conversion - returns the C++ object of the Python wrapper (keeps object identity).\n"; + + QString sourceTypeName = metaClass->name(); + QString targetTypeName = metaClass->name() + u"_PTR"_s; + StringStream c(TextStream::Language::Cpp); + c << "Shiboken::Conversions::pythonToCppPointer(" << cpythonType << ", pyIn, cppOut);"; + writePythonToCppFunction(s, c.toString(), sourceTypeName, targetTypeName); + + // "Is convertible" function for the Python object to C++ pointer conversion. + const QString pyTypeCheck = u"PyObject_TypeCheck(pyIn, "_s + cpythonType + u")"_s; + writeIsPythonConvertibleToCppFunction(s, sourceTypeName, targetTypeName, pyTypeCheck, QString(), true); + s << '\n'; + + // C++ pointer to a Python wrapper, keeping identity. + s << "// C++ to Python pointer conversion - tries to find the Python wrapper for the C++ object (keeps object identity).\n"; + c.clear(); + if (usePySideExtensions() && isQObject(metaClass)) { + c << "return PySide::getWrapperForQObject(reinterpret_cast<" + << typeName << " *>(const_cast<void *>(cppIn)), " << cpythonType << ");\n"; + } else { + writePointerToPythonConverter(c, metaClass, typeName, cpythonType); + } + std::swap(targetTypeName, sourceTypeName); + writeCppToPythonFunction(s, c.toString(), sourceTypeName, targetTypeName); + + // The conversions for an Object Type end here. + if (!typeEntry->isValue() && !typeEntry->isSmartPointer()) { + s << '\n'; + return; + } + + // Always copies C++ value (not pointer, and not reference) to a new Python wrapper. + s << '\n' << "// C++ to Python copy conversion.\n"; + targetTypeName = metaClass->name(); + + sourceTypeName = targetTypeName + u"_COPY"_s; + + c.clear(); + + const bool isUniquePointer = classContext.forSmartPointer() + && typeEntry->isUniquePointer(); + + if (isUniquePointer) { + c << "auto *source = reinterpret_cast<" << typeName + << " *>(const_cast<void *>(cppIn));\n"; + } else { + c << "auto *source = reinterpret_cast<const " << typeName << " *>(cppIn);\n"; + } + c << "return Shiboken::Object::newObject(" << cpythonType + << ", new " << globalScopePrefix(classContext) << classContext.effectiveClassName() << '(' + << (isUniquePointer ? "std::move(*source)" : "*source") + << "), true, true);"; + writeCppToPythonFunction(s, c.toString(), sourceTypeName, targetTypeName); + s << '\n'; + + // Python to C++ copy conversion. + s << "// Python to C++ copy conversion.\n"; + sourceTypeName = metaClass->name(); + + targetTypeName = sourceTypeName + "_COPY"_L1; + c.clear(); + + QString pyInVariable = u"pyIn"_s; + const QString outPtr = u"reinterpret_cast<"_s + typeName + u" *>(cppOut)"_s; + if (!classContext.forSmartPointer()) { + c << '*' << outPtr << " = *" + << cpythonWrapperCPtr(typeEntry, pyInVariable) << ';'; + } else { + auto ste = std::static_pointer_cast<const SmartPointerTypeEntry>(typeEntry); + const QString resetMethod = ste->resetMethod(); + c << "auto *ptr = " << outPtr << ";\n"; + c << "if (" << pyInVariable << " == Py_None)\n" << indent; + if (resetMethod.isEmpty()) + c << "*ptr = {};\n"; + else + c << "ptr->" << resetMethod << "();\n"; + const QString value = u'*' + cpythonWrapperCPtr(classContext.preciseType(), pyInVariable); + c << outdent << "else\n" << indent + << "*ptr = " << (isUniquePointer ? stdMove(value) : value) << ';'; + } + + writePythonToCppFunction(s, c.toString(), sourceTypeName, targetTypeName); + + // "Is convertible" function for the Python object to C++ value copy conversion. + QString copyTypeCheck = pyTypeCheck; + if (classContext.forSmartPointer()) + copyTypeCheck.prepend(pyInVariable + u" == Py_None || "_s); + writeIsPythonConvertibleToCppFunction(s, sourceTypeName, targetTypeName, copyTypeCheck); + s << '\n'; + + // User provided implicit conversions. + // Implicit conversions. + const AbstractMetaFunctionCList implicitConvs = implicitConversions(typeEntry); + + if (!implicitConvs.isEmpty()) + s << "// Implicit conversions.\n"; + + AbstractMetaType targetType = AbstractMetaType::fromAbstractMetaClass(metaClass); + for (const auto &conv : std::as_const(implicitConvs)) { + if (conv->isModifiedRemoved()) + continue; + + QString typeCheck; + QString toCppConv; + QString toCppPreConv; + if (conv->isConversionOperator()) { + const auto sourceClass = conv->ownerClass(); + typeCheck = u"PyObject_TypeCheck(pyIn, "_s + + cpythonTypeNameExt(sourceClass->typeEntry()) + u')'; + toCppConv = u'*' + cpythonWrapperCPtr(sourceClass->typeEntry(), + pyInVariable); + } else { + // Constructor that does implicit conversion. + const auto &firstArg = conv->arguments().constFirst(); + if (firstArg.isTypeModified() || conv->isModifiedToArray(1)) + continue; + const AbstractMetaType &sourceType = firstArg.type(); + if (sourceType.isWrapperType()) { + if (sourceType.referenceType() == LValueReference + || !sourceType.isPointerToWrapperType()) { + toCppConv = u" *"_s; + } + toCppConv += cpythonWrapperCPtr(sourceType.typeEntry(), pyInVariable); + } + + typeCheck = cpythonCheckFunction(sourceType); + if (typeCheck.endsWith(u", ")) { + typeCheck += pyInVariable + u')'; + } else if (typeCheck != u"true" && typeCheck != u"false") { + typeCheck += u'(' + pyInVariable + u')'; + } + + if (sourceType.isUserPrimitive() + || sourceType.isExtendedCppPrimitive() + || sourceType.typeEntry()->isContainer() + || sourceType.typeEntry()->isEnum() + || sourceType.typeEntry()->isFlags()) { + StringStream pc(TextStream::Language::Cpp); + pc << getFullTypeNameWithoutModifiers(sourceType) << " cppIn" + << minimalConstructorExpression(api(), sourceType) << ";\n"; + writeToCppConversion(pc, sourceType, pyInVariable, + u"cppIn"_s); + pc << ';'; + toCppPreConv = pc.toString(); + toCppConv.append(u"cppIn"_s); + } else if (!sourceType.isWrapperType()) { + StringStream tcc(TextStream::Language::Cpp); + writeToCppConversion(tcc, sourceType, pyInVariable, + u"/*BOZO-1061*/"_s); + toCppConv = tcc.toString(); + } + } + const AbstractMetaType sourceType = conv->isConversionOperator() + ? AbstractMetaType::fromAbstractMetaClass(conv->ownerClass()) + : conv->arguments().constFirst().type(); + writePythonToCppConversionFunctions(s, sourceType, targetType, typeCheck, toCppConv, toCppPreConv); + } + + if (typeEntry->isValue()) { + auto vte = std::static_pointer_cast<const ValueTypeEntry>(typeEntry); + writeCustomConverterFunctions(s, vte->customConversion()); + } +} + +void CppGenerator::writeCustomConverterFunctions(TextStream &s, + const CustomConversionPtr &customConversion) const +{ + if (!customConversion) + return; + const TargetToNativeConversions &toCppConversions = customConversion->targetToNativeConversions(); + if (toCppConversions.isEmpty()) + return; + auto ownerType = customConversion->ownerType(); + s << "// Python to C++ conversions for type '" << ownerType->qualifiedCppName() << "'.\n"; + for (const auto &toNative : toCppConversions) + writePythonToCppConversionFunctions(s, toNative, ownerType); + s << '\n'; +} + +void CppGenerator::writeConverterRegister(TextStream &s, const AbstractMetaClassCPtr &metaClass, + const GeneratorContext &classContext) const +{ + const auto typeEntry = metaClass->typeEntry(); + if (typeEntry->isNamespace()) + return; + s << "// Register Converter\n" + << "SbkConverter *converter = Shiboken::Conversions::createConverter(pyType,\n" + << indent; + QString sourceTypeName = metaClass->name(); + QString targetTypeName = sourceTypeName + u"_PTR"_s; + s << pythonToCppFunctionName(sourceTypeName, targetTypeName) << ',' << '\n' + << convertibleToCppFunctionName(sourceTypeName, targetTypeName) << ',' << '\n'; + std::swap(targetTypeName, sourceTypeName); + s << cppToPythonFunctionName(sourceTypeName, targetTypeName); + if (typeEntry->isValue() || typeEntry->isSmartPointer()) { + s << ',' << '\n'; + sourceTypeName = metaClass->name() + u"_COPY"_s; + s << cppToPythonFunctionName(sourceTypeName, targetTypeName); + } + s << outdent << ");\n\n"; + + auto writeConversions = [&s](const QString &signature) + { + s << registerConverterName(signature) << registerConverterName(signature + u'*') + << registerConverterName(signature + u'&'); + }; + + auto writeConversionsForType = [writeConversions](const QString &fullTypeName) + { + QStringList lst = fullTypeName.split(u"::"_s, + Qt::SkipEmptyParts); + while (!lst.isEmpty()) { + QString signature = lst.join(u"::"_s); + writeConversions(signature); + lst.removeFirst(); + } + }; + + + if (!classContext.forSmartPointer()) { + writeConversionsForType(metaClass->qualifiedCppName()); + } else { + const QString &smartPointerType = classContext.preciseType().instantiations().at(0).cppSignature(); + const QString &smartPointerName = classContext.preciseType().typeEntry()->name(); + + QStringList lst = smartPointerType.split(u"::"_s, + Qt::SkipEmptyParts); + while (!lst.isEmpty()) { + QString signature = lst.join(u"::"_s); + writeConversions(smartPointerName + u'<' + signature + u'>'); + lst.removeFirst(); + } + + writeConversionsForType(smartPointerType); + } + + s << "Shiboken::Conversions::registerConverterName(converter, typeid(" << m_gsp; + QString qualifiedCppNameInvocation; + if (!classContext.forSmartPointer()) + qualifiedCppNameInvocation = metaClass->qualifiedCppName(); + else + qualifiedCppNameInvocation = classContext.preciseType().cppSignature(); + + s << qualifiedCppNameInvocation << ").name());\n"; + + if (classContext.useWrapper()) { + s << "Shiboken::Conversions::registerConverterName(converter, typeid(" + << classContext.wrapperName() << ").name());\n"; + } + + if (!typeEntry->isValue() && !typeEntry->isSmartPointer()) + return; + + // Python to C++ copy (value, not pointer neither reference) conversion. + s << "\n// Add Python to C++ copy (value, not pointer neither reference) conversion to type converter.\n"; + sourceTypeName = metaClass->name(); + targetTypeName = sourceTypeName + u"_COPY"_s; + QString toCpp = pythonToCppFunctionName(sourceTypeName, targetTypeName); + QString isConv = convertibleToCppFunctionName(sourceTypeName, targetTypeName); + writeAddPythonToCppConversion(s, u"converter"_s, toCpp, isConv); + + // User provided implicit conversions. + + // Add implicit conversions. + const AbstractMetaFunctionCList implicitConvs = implicitConversions(typeEntry); + + if (!implicitConvs.isEmpty()) + s << "// Add implicit conversions to type converter.\n"; + + AbstractMetaType targetType = AbstractMetaType::fromAbstractMetaClass(metaClass); + for (const auto &conv : std::as_const(implicitConvs)) { + if (conv->isModifiedRemoved()) + continue; + AbstractMetaType sourceType; + if (conv->isConversionOperator()) { + sourceType = AbstractMetaType::fromAbstractMetaClass(conv->ownerClass()); + } else { + // Constructor that does implicit conversion. + const auto &firstArg = conv->arguments().constFirst(); + if (firstArg.isTypeModified() || conv->isModifiedToArray(1)) + continue; + sourceType = firstArg.type(); + } + QString toCpp = pythonToCppFunctionName(sourceType, targetType); + QString isConv = convertibleToCppFunctionName(sourceType, targetType); + writeAddPythonToCppConversion(s, u"converter"_s, toCpp, isConv); + } + + if (typeEntry->isValue()) { + auto vte = std::static_pointer_cast<const ValueTypeEntry>(typeEntry); + writeCustomConverterRegister(s, vte->customConversion(), u"converter"_s); + } +} + +void CppGenerator::writeCustomConverterRegister(TextStream &s, + const CustomConversionPtr &customConversion, + const QString &converterVar) +{ + if (!customConversion) + return; + const TargetToNativeConversions &toCppConversions = + customConversion->targetToNativeConversions(); + if (toCppConversions.isEmpty()) + return; + s << "// Add user defined implicit conversions to type converter.\n"; + for (const auto &toNative : toCppConversions) { + QString toCpp = pythonToCppFunctionName(toNative, customConversion->ownerType()); + QString isConv = convertibleToCppFunctionName(toNative, customConversion->ownerType()); + writeAddPythonToCppConversion(s, converterVar, toCpp, isConv); + } +} + +void CppGenerator::writeContainerConverterFunctions(TextStream &s, + const AbstractMetaType &containerType) const +{ + writeCppToPythonFunction(s, containerType); + writePythonToCppConversionFunctions(s, containerType); +} + +bool CppGenerator::needsArgumentErrorHandling(const OverloadData &overloadData) +{ + if (overloadData.maxArgs() > 0) + return true; + // QObject constructors need error handling when passing properties as kwarg. + if (!usePySideExtensions()) + return false; + auto rfunc = overloadData.referenceFunction(); + return rfunc->functionType() == AbstractMetaFunction::ConstructorFunction + && isQObject(rfunc->ownerClass()); +} + +void CppGenerator::writeMethodWrapperPreamble(TextStream &s, + const OverloadData &overloadData, + const GeneratorContext &context, + ErrorReturn errorReturn) +{ + const auto rfunc = overloadData.referenceFunction(); + const auto ownerClass = rfunc->targetLangOwner(); + Q_ASSERT(ownerClass == context.metaClass()); + int minArgs = overloadData.minArgs(); + int maxArgs = overloadData.maxArgs(); + bool initPythonArguments; + + // If method is a constructor... + if (rfunc->isConstructor()) { + // Check if the right constructor was called. + if (!ownerClass->hasPrivateDestructor()) { + s << "if (Shiboken::Object::isUserType(self) && " + << "!Shiboken::ObjectType::canCallConstructor(self->ob_type, Shiboken::SbkType< " + << m_gsp; + QString qualifiedCppName; + if (!context.forSmartPointer()) + qualifiedCppName = ownerClass->qualifiedCppName(); + else + qualifiedCppName = context.preciseType().cppSignature(); + + s << qualifiedCppName << " >()))\n" << indent << errorReturn << outdent << '\n'; + } + // Declare pointer for the underlying C++ object. + s << globalScopePrefix(context) << context.effectiveClassName() << " *cptr{};\n"; + + initPythonArguments = maxArgs > 0; + + } else { + if (rfunc->implementingClass() && + (!rfunc->implementingClass()->isNamespace() && overloadData.hasInstanceFunction())) { + CppSelfDefinitionFlags flags; + if (overloadData.hasStaticFunction()) + flags.setFlag(CppSelfDefinitionFlag::HasStaticOverload); + if (overloadData.hasClassMethod()) + flags.setFlag(CppSelfDefinitionFlag::HasClassMethodOverload); + writeCppSelfDefinition(s, rfunc, context, errorReturn, flags); + } + if (!rfunc->isInplaceOperator() && overloadData.hasNonVoidReturnType()) + s << "PyObject *" << PYTHON_RETURN_VAR << "{};\n"; + + initPythonArguments = minArgs != maxArgs || maxArgs > 1; + } + + if (needsArgumentErrorHandling(overloadData)) + s << "Shiboken::AutoDecRef errInfo{};\n"; + + s << "static const char fullName[] = \"" << fullPythonFunctionName(rfunc, true) + << "\";\nSBK_UNUSED(fullName)\n" + << "Shiboken::PythonContextMarker pcm;\n"; + // PYSIDE-2335: Mark blocking calls like `exec` or `run` as such. + bool isBlockingFunction = rfunc->name() == u"exec"_s || rfunc->name() == u"exec_"_s + || rfunc->name() == u"run"_s; + if (isBlockingFunction) + s << "pcm.setBlocking();\n"; + + if (maxArgs > 0) { + s << "int overloadId = -1;\n" + << PYTHON_TO_CPPCONVERSION_STRUCT << ' ' << PYTHON_TO_CPP_VAR; + if (overloadData.pythonFunctionWrapperUsesListOfArguments()) + s << '[' << maxArgs << ']'; + s << ";\n" << sbkUnusedVariableCast(PYTHON_TO_CPP_VAR); + } + + if (initPythonArguments) { + s << "const Py_ssize_t numArgs = "; + if (minArgs == 0 && maxArgs == 1 && !rfunc->isConstructor() + && !overloadData.pythonFunctionWrapperUsesListOfArguments()) { + s << "(" << PYTHON_ARG << " == 0 ? 0 : 1);\n"; + } else { + writeArgumentsInitializer(s, overloadData, errorReturn); + } + } +} + +void CppGenerator::writeConstructorWrapper(TextStream &s, const OverloadData &overloadData, + const GeneratorContext &classContext) const +{ + const ErrorReturn errorReturn = ErrorReturn::MinusOne; + + const auto rfunc = overloadData.referenceFunction(); + const auto metaClass = rfunc->ownerClass(); + + s << "static int\n"; + s << cpythonFunctionName(rfunc) + << "(PyObject *self, PyObject *args, PyObject *kwds)\n{\n" << indent; + if (overloadData.maxArgs() == 0 || metaClass->isAbstract()) + s << sbkUnusedVariableCast("args"); + s << sbkUnusedVariableCast("kwds"); + + const bool needsMetaObject = usePySideExtensions() && isQObject(metaClass); + if (needsMetaObject) + s << "const QMetaObject *metaObject;\n"; + + s << "auto *sbkSelf = reinterpret_cast<SbkObject *>(self);\n"; + + if (metaClass->isAbstract() || metaClass->baseClassNames().size() > 1) { + s << "PyTypeObject *type = self->ob_type;\n" + << "PyTypeObject *myType = " + << cpythonTypeNameExt(metaClass->typeEntry()) << ";\n"; + } + + if (metaClass->isAbstract()) { + // C++ Wrapper disabled: Abstract C++ class cannot be instantiated. + if (metaClass->typeEntry()->typeFlags().testFlag(ComplexTypeEntry::DisableWrapper)) { + s << sbkUnusedVariableCast("sbkSelf") + << sbkUnusedVariableCast("type") + << sbkUnusedVariableCast("myType"); + if (needsMetaObject) + s << sbkUnusedVariableCast("metaObject"); + s << "Shiboken::Errors::setInstantiateAbstractClassDisabledWrapper(\"" + << metaClass->qualifiedCppName() << "\");\n" << errorReturn << outdent + << "}\n\n"; + return; + } + + // Refuse to instantiate Abstract C++ class (via C++ Wrapper) unless it is + // a Python-derived class for which type != myType. + s << "if (type == myType) {\n" << indent + << "Shiboken::Errors::setInstantiateAbstractClass(\"" << metaClass->qualifiedCppName() + << "\");\n" << errorReturn << outdent + << "}\n\n"; + } + + if (metaClass->baseClassNames().size() > 1) { + if (!metaClass->isAbstract()) + s << "if (type != myType)\n" << indent; + s << "Shiboken::ObjectType::copyMultipleInheritance(type, myType);\n"; + if (!metaClass->isAbstract()) + s << outdent << '\n'; + } + + // PYSIDE-1478: Switching must also happen at object creation time. + if (usePySideExtensions() && !classContext.forSmartPointer()) + s << "PySide::Feature::Select(self);\n"; + + writeMethodWrapperPreamble(s, overloadData, classContext, errorReturn); + + s << '\n'; + + if (overloadData.maxArgs() > 0) + writeOverloadedFunctionDecisor(s, overloadData, errorReturn); + + // Handles Python Multiple Inheritance + QString pre = needsMetaObject ? u"bool usesPyMI = "_s : u""_s; + s << "\n// PyMI support\n" + << pre << "Shiboken::callInheritedInit(self, args, kwds, fullName);\n" + << "if (" << shibokenErrorsOccurred << ")\n" + << indent << errorReturn << outdent << "\n"; + + writeFunctionCalls(s, overloadData, classContext, errorReturn); + s << '\n'; + + const QString typeName = classContext.forSmartPointer() + ? classContext.preciseType().cppSignature() : metaClass->qualifiedCppName(); + s << "if (" << shibokenErrorsOccurred + << " || !Shiboken::Object::setCppPointer(sbkSelf, Shiboken::SbkType< " + << globalScopePrefix(classContext) << typeName << " >(), cptr)) {\n" + << indent << "delete cptr;\n" << errorReturn << outdent + << "}\n"; + if (overloadData.maxArgs() > 0) + s << "if (cptr == nullptr)\n" << indent + << "return " << returnErrorWrongArguments(overloadData, errorReturn) << ";\n\n" + << outdent; + + s << "Shiboken::Object::setValidCpp(sbkSelf, true);\n"; + // If the created C++ object has a C++ wrapper the ownership is assigned to Python + // (first "1") and the flag indicating that the Python wrapper holds an C++ wrapper + // is marked as true (the second "1"). Otherwise the default values apply: + // Python owns it and C++ wrapper is false. + if (shouldGenerateCppWrapper(overloadData.referenceFunction()->ownerClass())) + s << "Shiboken::Object::setHasCppWrapper(sbkSelf, true);\n"; + // Need to check if a wrapper for same pointer is already registered + // Caused by bug PYSIDE-217, where deleted objects' wrappers are not released + s << "if (Shiboken::BindingManager::instance().hasWrapper(cptr)) {\n" << indent + << "Shiboken::BindingManager::instance().releaseWrapper(" + "Shiboken::BindingManager::instance().retrieveWrapper(cptr));\n" << outdent + << "}\nShiboken::BindingManager::instance().registerWrapper(sbkSelf, cptr);\n"; + + // Create metaObject and register signal/slot + if (needsMetaObject) { + s << "\n// QObject setup\n" + << "PySide::Signal::updateSourceObject(self);\n" + << "metaObject = cptr->metaObject(); // <- init python qt properties\n" + << "if (!errInfo.isNull() && PyDict_Check(errInfo.object())) {\n" << indent + << "if (!PySide::fillQtProperties(self, metaObject, errInfo, usesPyMI))\n" << indent + << "return " << returnErrorWrongArguments(overloadData, errorReturn) << ";\n" + << outdent << outdent + << "};\n"; + } + + // Constructor code injections, position=end + bool hasCodeInjectionsAtEnd = false; + for (const auto &func : overloadData.overloads()) { + const CodeSnipList &injectedCodeSnips = func->injectedCodeSnips(); + for (const CodeSnip &cs : injectedCodeSnips) { + if (cs.position == TypeSystem::CodeSnipPositionEnd) { + hasCodeInjectionsAtEnd = true; + break; + } + } + } + if (hasCodeInjectionsAtEnd) { + // FIXME: C++ arguments are not available in code injection on constructor when position = end. + s << "switch (overloadId) {\n"; + for (const auto &func : overloadData.overloads()) { + s << indent; + const CodeSnipList &injectedCodeSnips = func->injectedCodeSnips(); + for (const CodeSnip &cs : injectedCodeSnips) { + if (cs.position == TypeSystem::CodeSnipPositionEnd) { + s << "case " << metaClass->functions().indexOf(func) << ':' << '\n' + << "{\n" << indent; + writeCodeSnips(s, func->injectedCodeSnips(), TypeSystem::CodeSnipPositionEnd, + TypeSystem::TargetLangCode, func, + true /* usesPyArgs */, nullptr); + s << outdent << "}\nbreak;\n"; + break; + } + } + s << outdent; + } + s << "}\n"; + } + + s << "\n\nreturn 1;\n"; + s<< outdent << "}\n\n"; +} + +void CppGenerator::writeMethodWrapper(TextStream &s, const OverloadData &overloadData, + const GeneratorContext &classContext) const +{ + const auto rfunc = overloadData.referenceFunction(); + + int maxArgs = overloadData.maxArgs(); + + s << "static PyObject *"; + s << cpythonFunctionName(rfunc) << "(PyObject *self"; + bool hasKwdArgs = false; + if (maxArgs > 0) { + s << ", PyObject *" + << (overloadData.pythonFunctionWrapperUsesListOfArguments() ? u"args"_s : PYTHON_ARG); + hasKwdArgs = overloadData.hasArgumentWithDefaultValue() || rfunc->isCallOperator(); + if (hasKwdArgs) + s << ", PyObject *kwds"; + } + s << ")\n{\n" << indent; + if (rfunc->ownerClass() == nullptr || overloadData.hasStaticFunction()) + s << sbkUnusedVariableCast(PYTHON_SELF_VAR); + if (hasKwdArgs) + s << sbkUnusedVariableCast("kwds"); + + writeMethodWrapperPreamble(s, overloadData, classContext); + + s << '\n'; + + // This code is intended for shift operations only: Make sure reverse <</>> + // operators defined in other classes (specially from other modules) + // are called. A proper and generic solution would require an reengineering + // in the operator system like the extended converters. + // Solves #119 - QDataStream <</>> operators not working for QPixmap. + const bool hasReturnValue = overloadData.hasNonVoidReturnType(); + + if (hasReturnValue && rfunc->functionType() == AbstractMetaFunction::ShiftOperator + && rfunc->isBinaryOperator()) { + // For custom classes, operations like __radd__ and __rmul__ + // will enter an infinite loop. + const QString pythonOp = ShibokenGenerator::pythonOperatorFunctionName(rfunc); + s << "static PyObject *attrName = Shiboken::PyMagicName::r" + << pythonOp.mid(2, pythonOp.size() -4) << "();\n" // Strip __ + << "if (!isReverse\n" << indent + << "&& Shiboken::Object::checkType(" << PYTHON_ARG << ")\n" + << "&& !PyObject_TypeCheck(" << PYTHON_ARG << ", self->ob_type)\n" + << "&& PyObject_HasAttr(" << PYTHON_ARG << ", attrName)) {\n" + << "PyObject *revOpMethod = PyObject_GetAttr(" << PYTHON_ARG << ", attrName);\n" + << "if (revOpMethod && PyCallable_Check(revOpMethod)) {\n" << indent + << PYTHON_RETURN_VAR << " = PyObject_CallFunction(revOpMethod, \"O\", self);\n" + << "if (" << shibokenErrorsOccurred + << " && (PyErr_ExceptionMatches(PyExc_NotImplementedError)" + << " || PyErr_ExceptionMatches(PyExc_AttributeError))) {\n" << indent + << "PyErr_Clear();\n" + << "Py_XDECREF(" << PYTHON_RETURN_VAR << ");\n" + << PYTHON_RETURN_VAR << " = " << NULL_PTR << ";\n" + << outdent << "}\n" + << outdent << "}\n" + << "Py_XDECREF(revOpMethod);\n\n" + << outdent << "}\n\n" + << "// Do not enter here if other object has implemented a reverse operator.\n" + << "if (" << PYTHON_RETURN_VAR << " == nullptr) {\n" << indent; + if (maxArgs > 0) + writeOverloadedFunctionDecisor(s, overloadData, ErrorReturn::Default); + writeFunctionCalls(s, overloadData, classContext, ErrorReturn::Default); + s << outdent << '\n' << "} // End of \"if (!" << PYTHON_RETURN_VAR << ")\"\n"; + } else { // binary shift operator + if (maxArgs > 0) + writeOverloadedFunctionDecisor(s, overloadData, ErrorReturn::Default); + writeFunctionCalls(s, overloadData, classContext, ErrorReturn::Default); + } + + s << '\n'; + + writeFunctionReturnErrorCheckSection(s, ErrorReturn::Default, + hasReturnValue && !rfunc->isInplaceOperator()); + + if (hasReturnValue) { + if (rfunc->isInplaceOperator()) { + s << "Py_INCREF(self);\nreturn self;\n"; + } else { + s << "return " << PYTHON_RETURN_VAR << ";\n"; + } + } else { + s << "Py_RETURN_NONE;\n"; + } + + s<< outdent << "}\n\n"; +} + +void CppGenerator::writeArgumentsInitializer(TextStream &s, const OverloadData &overloadData, + ErrorReturn errorReturn) +{ + const auto rfunc = overloadData.referenceFunction(); + s << "PyTuple_GET_SIZE(args);\n" << sbkUnusedVariableCast("numArgs"); + + int minArgs = overloadData.minArgs(); + int maxArgs = overloadData.maxArgs(); + + s << "PyObject *"; + s << PYTHON_ARGS << "[] = {" + << QByteArrayList(maxArgs, "nullptr").join(", ") + << "};\n\n"; + + if (overloadData.hasVarargs()) { + maxArgs--; + if (minArgs > maxArgs) + minArgs = maxArgs; + + s << "PyObject *nonvarargs = PyTuple_GetSlice(args, 0, " << maxArgs << ");\n" + << "Shiboken::AutoDecRef auto_nonvarargs(nonvarargs);\n" + << PYTHON_ARGS << '[' << maxArgs << "] = PyTuple_GetSlice(args, " + << maxArgs << ", numArgs);\n" + << "Shiboken::AutoDecRef auto_varargs(" << PYTHON_ARGS << "[" + << maxArgs << "]);\n\n"; + } + + bool usesNamedArguments = overloadData.hasArgumentWithDefaultValue(); + + s << "// invalid argument lengths\n"; + + // Disable argument count checks for QObject constructors to allow for + // passing properties as KW args. + const auto owner = rfunc->ownerClass(); + bool isQObjectConstructor = owner && isQObject(owner) + && rfunc->functionType() == AbstractMetaFunction::ConstructorFunction; + + if (usesNamedArguments && !isQObjectConstructor) { + s << "errInfo.reset(Shiboken::checkInvalidArgumentCount(numArgs, " + << minArgs << ", " << maxArgs << "));\n" + << "if (!errInfo.isNull())\n" << indent + << "return " << returnErrorWrongArguments(overloadData, errorReturn) << ";\n" + << outdent; + } + + const QList<int> invalidArgsLength = overloadData.invalidArgumentLengths(); + if (!invalidArgsLength.isEmpty()) { + s << "if ("; + for (qsizetype i = 0, size = invalidArgsLength.size(); i < size; ++i) { + if (i) + s << " || "; + s << "numArgs == " << invalidArgsLength.at(i); + } + s << ")\n" << indent + << "return " << returnErrorWrongArguments(overloadData, errorReturn) << ";\n" + << outdent; + } + s << '\n'; + + QString funcName; + if (rfunc->isOperatorOverload()) + funcName = ShibokenGenerator::pythonOperatorFunctionName(rfunc); + else + funcName = rfunc->name(); + + QString argsVar = overloadData.hasVarargs() ? u"nonvarargs"_s : u"args"_s; + s << "if ("; + if (usesNamedArguments) { + s << "PyArg_ParseTuple(" << argsVar << ", \"|" << QByteArray(maxArgs, 'O') + << ':' << funcName << '"'; + } else { + s << "PyArg_UnpackTuple(" << argsVar << ", \"" << funcName << "\", " + << minArgs << ", " << maxArgs; + } + for (int i = 0; i < maxArgs; i++) + s << ", &(" << PYTHON_ARGS << '[' << i << "])"; + s << ") == 0)\n" << indent << errorReturn << outdent << '\n'; +} + +void CppGenerator::writeCppSelfConversion(TextStream &s, const GeneratorContext &context, + const QString &className, bool useWrapperClass) +{ + if (context.forSmartPointer()) { + writeSmartPointerCppSelfConversion(s, context); + return; + } + + if (useWrapperClass) + s << "static_cast<" << className << " *>("; + s << cpythonWrapperCPtr(context.metaClass(), PYTHON_SELF_VAR); + if (useWrapperClass) + s << ')'; +} + +void CppGenerator::writeCppSelfVarDef(TextStream &s, + CppSelfDefinitionFlags flags) +{ + if (flags.testFlag(CppGenerator::CppSelfAsReference)) + s << "auto &" << CPP_SELF_VAR << " = *"; + else + s << "auto *" << CPP_SELF_VAR << " = "; +} + +void CppGenerator::writeCppSelfDefinition(TextStream &s, + const GeneratorContext &context, + ErrorReturn errorReturn, + CppSelfDefinitionFlags flags) +{ + Q_ASSERT(!(flags.testFlag(CppSelfAsReference) && flags.testFlag(HasStaticOverload))); + if (context.forSmartPointer()) { + writeSmartPointerCppSelfDefinition(s, context, errorReturn, flags); + return; + } + + AbstractMetaClassCPtr metaClass = context.metaClass(); + const auto cppWrapper = context.metaClass()->cppWrapper(); + // In the Python method, use the wrapper to access the protected + // functions. + const bool useWrapperClass = avoidProtectedHack() + && cppWrapper.testFlag(AbstractMetaClass::CppProtectedHackWrapper); + Q_ASSERT(!useWrapperClass || context.useWrapper()); + const QString className = useWrapperClass + ? context.wrapperName() : getFullTypeName(metaClass); + + writeInvalidPyObjectCheck(s, PYTHON_SELF_VAR, errorReturn); + + if (flags.testFlag(CppSelfAsReference)) { + writeCppSelfVarDef(s, flags); + writeCppSelfConversion(s, context, className, useWrapperClass); + s << ";\n"; + return; + } + + if (!flags.testFlag(HasStaticOverload)) { + if (!flags.testFlag(HasClassMethodOverload)) { + // PYSIDE-131: The single case of a class method for now: tr(). + writeCppSelfVarDef(s, flags); + writeCppSelfConversion(s, context, className, useWrapperClass); + s << ";\n" << sbkUnusedVariableCast(CPP_SELF_VAR); + } + return; + } + + s << className << " *" << CPP_SELF_VAR << " = nullptr;\n" + << sbkUnusedVariableCast(CPP_SELF_VAR); + + // Checks if the underlying C++ object is valid. + s << "if (self)\n" << indent + << CPP_SELF_VAR << " = "; + writeCppSelfConversion(s, context, className, useWrapperClass); + s << ";\n"<< outdent; +} + +void CppGenerator::writeCppSelfDefinition(TextStream &s, + const AbstractMetaFunctionCPtr &func, + const GeneratorContext &context, + ErrorReturn errorReturn, + CppSelfDefinitionFlags flags) +{ + if (!func->ownerClass() || func->isConstructor()) + return; + + if (func->isOperatorOverload() && func->isBinaryOperator()) { + QString checkFunc = cpythonCheckFunction(func->ownerClass()->typeEntry()); + s << "bool isReverse = " << checkFunc << PYTHON_ARG << ")\n" + << " && !" << checkFunc << "self);\n" + << "if (isReverse)\n" << indent + << "std::swap(self, " << PYTHON_ARG << ");\n" << outdent; + } + + writeCppSelfDefinition(s, context, errorReturn, flags); +} + +QString CppGenerator::returnErrorWrongArguments(const OverloadData &overloadData, + ErrorReturn errorReturn) +{ + const auto rfunc = overloadData.referenceFunction(); + QString argsVar = overloadData.pythonFunctionWrapperUsesListOfArguments() + ? u"args"_s : PYTHON_ARG; + switch (errorReturn) { + case ErrorReturn::Default: + return u"Shiboken::returnWrongArguments("_s + argsVar + u", fullName, errInfo)"_s; + case ErrorReturn::Zero: + return u"Shiboken::returnWrongArguments_Zero("_s + argsVar + u", fullName, errInfo)"_s; + case ErrorReturn::MinusOne: + return u"Shiboken::returnWrongArguments_MinusOne("_s + argsVar + u", fullName, errInfo)"_s; + case ErrorReturn::Void: + Q_ASSERT(false); + } + return {}; +} + +void CppGenerator::writeFunctionReturnErrorCheckSection(TextStream &s, + ErrorReturn errorReturn, + bool hasReturnValue) +{ + s << "if (" << shibokenErrorsOccurred; + if (hasReturnValue) + s << " || " << PYTHON_RETURN_VAR << " == nullptr"; + s << ") {\n" << indent; + if (hasReturnValue) + s << "Py_XDECREF(" << PYTHON_RETURN_VAR << ");\n"; + s << errorReturn << outdent << "}\n"; +} + +void CppGenerator::writeInvalidPyObjectCheck(TextStream &s, const QString &pyObj, + ErrorReturn errorReturn) +{ + s << "if (!Shiboken::Object::isValid(" << pyObj << "))\n" + << indent << errorReturn << outdent; +} + +static QString pythonToCppConverterForArgumentName(const QString &argumentName) +{ + static const QRegularExpression pyArgsRegex(PYTHON_ARGS + + uR"((\[\d+[-]?\d*\]))"_s); + Q_ASSERT(pyArgsRegex.isValid()); + const QRegularExpressionMatch match = pyArgsRegex.match(argumentName); + QString result = PYTHON_TO_CPP_VAR; + if (match.hasMatch()) + result += match.captured(1); + return result; +} + +void CppGenerator::writeTypeCheck(TextStream &s, const QString &customType, + const QString &argumentName) +{ + QString errorMessage; + const auto metaTypeOpt = AbstractMetaType::fromString(customType, &errorMessage); + if (!metaTypeOpt.has_value()) + throw Exception(errorMessage); + writeTypeCheck(s, metaTypeOpt.value(), argumentName, + ShibokenGenerator::isNumber(metaTypeOpt.value())); +} + +void CppGenerator::writeTypeCheck(TextStream &s, const AbstractMetaType &argType, + const QString &argumentName, bool isNumber, + bool rejectNull) +{ + // TODO-CONVERTER: merge this with the code below. + QString typeCheck = cpythonIsConvertibleFunction(argType); + if (typeCheck != u"true") // For PyObject, which is always true + typeCheck.append(u'(' +argumentName + u')'); + + // TODO-CONVERTER ----------------------------------------------------------------------- + if (!argType.typeEntry()->isCustom()) { + typeCheck = u'(' + pythonToCppConverterForArgumentName(argumentName) + + u" = "_s + typeCheck + u"))"_s; + if (!isNumber && isCppPrimitive(argType.typeEntry())) { + typeCheck.prepend(cpythonCheckFunction(argType) + u'(' + + argumentName + u") && "_s); + } + } + // TODO-CONVERTER ----------------------------------------------------------------------- + + if (rejectNull) + typeCheck = u'(' + argumentName + u" != Py_None && "_s + typeCheck + u')'; + + s << typeCheck; +} + +static void checkTypeViability(const AbstractMetaFunctionCPtr &func, + const AbstractMetaType &type, int argIdx) +{ + const bool modified = argIdx == 0 + ? func->isTypeModified() + : func->arguments().at(argIdx -1).isTypeModified(); + const bool isRemoved = argIdx == 0 + ? func->argumentRemoved(0) + : func->arguments().at(argIdx -1).isModifiedRemoved(); + if (type.isVoid() + || !type.typeEntry()->isPrimitive() + || type.indirections() == 0 + || (type.indirections() == 1 && type.typeUsagePattern() == AbstractMetaType::NativePointerAsArrayPattern) + || type.isCString() + || isRemoved + || modified + || func->hasConversionRule(TypeSystem::All, argIdx) + || func->hasInjectedCode()) + return; + QString message; + QTextStream str(&message); + str << func->sourceLocation() + << "There's no user provided way (conversion rule, argument" + " removal, custom code, etc) to handle the primitive "; + if (argIdx == 0) + str << "return type '" << type.cppSignature() << '\''; + else + str << "type '" << type.cppSignature() << "' of argument " << argIdx; + str << " in function '"; + if (func->ownerClass()) + str << func->ownerClass()->qualifiedCppName() << "::"; + str << func->signature() << "'."; + qCWarning(lcShiboken).noquote().nospace() << message; +} + +static void checkTypeViability(const AbstractMetaFunctionCPtr &func) +{ + if (func->isUserAdded()) + return; + checkTypeViability(func, func->type(), 0); + for (qsizetype i = 0; i < func->arguments().size(); ++i) + checkTypeViability(func, func->arguments().at(i).type(), int(i + 1)); +} + +void CppGenerator::writeTypeCheck(TextStream &s, + const std::shared_ptr<OverloadDataNode> &overloadData, + const QString &argumentName) +{ + QSet<TypeEntryCPtr> numericTypes; + const OverloadDataList &siblings = overloadData->parent()->children(); + for (const auto &sibling : siblings) { + for (const auto &func : sibling->overloads()) { + checkTypeViability(func); + const AbstractMetaType &argType = sibling->overloadArgument(func)->type(); + if (!argType.isPrimitive()) + continue; + if (ShibokenGenerator::isNumber(argType.typeEntry())) + numericTypes << argType.typeEntry(); + } + } + + // This condition trusts that the OverloadData object will arrange for + // PyLong type to come after the more precise numeric types (e.g. float and bool) + AbstractMetaType argType = overloadData->modifiedArgType(); + if (auto viewOn = argType.viewOn()) + argType = *viewOn; + const bool numberType = numericTypes.size() == 1 || ShibokenGenerator::isPyInt(argType); + bool rejectNull = + shouldRejectNullPointerArgument(overloadData->referenceFunction(), overloadData->argPos()); + writeTypeCheck(s, argType, argumentName, numberType, rejectNull); +} + +qsizetype CppGenerator::writeArgumentConversion(TextStream &s, + const AbstractMetaType &argType, + const QString &argName, + const QString &pyArgName, + ErrorReturn errorReturn, + const AbstractMetaClassCPtr &context, + const QString &defaultValue, + bool castArgumentAsUnused) const +{ + qsizetype result = 0; + if (argType.typeEntry()->isCustom() || argType.typeEntry()->isVarargs()) + return result; + if (argType.isWrapperType()) + writeInvalidPyObjectCheck(s, pyArgName, errorReturn); + result = writePythonToCppTypeConversion(s, argType, pyArgName, argName, context, defaultValue); + if (castArgumentAsUnused) + s << sbkUnusedVariableCast(argName); + return result; +} + +AbstractMetaType + CppGenerator::getArgumentType(const AbstractMetaFunctionCPtr &func, int index) +{ + if (index < 0 || index >= func->arguments().size()) { + qCWarning(lcShiboken).noquote().nospace() + << "Argument index for function '" << func->signature() << "' out of range."; + return {}; + } + + auto argType = func->arguments().at(index).modifiedType(); + return argType.viewOn() ? *argType.viewOn() : argType; +} + +static inline QString arrayHandleType(const AbstractMetaTypeList &nestedArrayTypes) +{ + switch (nestedArrayTypes.size()) { + case 1: + return "Shiboken::Conversions::ArrayHandle<"_L1 + + nestedArrayTypes.constLast().minimalSignature() + u'>'; + case 2: + return "Shiboken::Conversions::Array2Handle<"_L1 + + nestedArrayTypes.constLast().minimalSignature() + + ", "_L1 + + QString::number(nestedArrayTypes.constFirst().arrayElementCount()) + + u'>'; + } + return QString(); +} + +// Helper to write argument initialization code for a function argument +// in case it has a default value. +template <class Type> // AbstractMetaType/TypeEntry +static void writeMinimalConstructorExpression(TextStream &s, + const ApiExtractorResult &api, + Type type, + bool isPrimitive, + const QString &defaultValue) +{ + if (defaultValue.isEmpty()) { + s << ShibokenGenerator::minimalConstructorExpression(api, type); + return; + } + // Use assignment to avoid "Most vexing parse" if it looks like + // a function call, or for primitives/pointers + const bool isDefault = defaultValue == u"{}"; + if ((isPrimitive && !isDefault) + || defaultValue == u"nullptr" || defaultValue.contains(u'(')) { + s << " = " << defaultValue; + return; + } + if (isDefault) { + s << defaultValue; + return; + } + s << '(' << defaultValue << ')'; +} + +qsizetype CppGenerator::writePythonToCppTypeConversion(TextStream &s, + const AbstractMetaType &type, + const QString &pyIn, + const QString &cppOut, + const AbstractMetaClassCPtr &context, + const QString &defaultValue) const +{ + TypeEntryCPtr typeEntry = type.typeEntry(); + if (typeEntry->isCustom() || typeEntry->isVarargs()) + return 0; + + const auto arg = GeneratorArgument::fromMetaType(type); + const bool isPrimitive = arg.type == GeneratorArgument::Type::Primitive; + + QString cppOutAux = cppOut + u"_local"_s; + + QString typeName = arg.type == GeneratorArgument::Type::CppPrimitiveArray + ? arrayHandleType(type.nestedArrayTypes()) + : getFullTypeNameWithoutModifiers(type); + + bool isProtectedEnum = false; + if (arg.type == GeneratorArgument::Type::Enum && avoidProtectedHack()) { + auto metaEnum = api().findAbstractMetaEnum(type.typeEntry()); + if (metaEnum.has_value() && metaEnum->isProtected()) { + typeName = wrapperName(context) + u"::"_s + + metaEnum.value().name(); + isProtectedEnum = true; + } + } + + s << typeName; + switch (arg.conversion) { + case GeneratorArgument::Conversion::CppPrimitiveArray: + s << ' ' << cppOut; + break; + case GeneratorArgument::Conversion::ValueOrPointer: { + // Generate either value conversion for &cppOutAux or pointer + // conversion for &cppOut + s << ' ' << cppOutAux; + // No default value for containers which can also be passed by pointer. + if (arg.type != GeneratorArgument::Type::Container || type.indirections() == 0) + writeMinimalConstructorExpression(s, api(), type, isPrimitive, defaultValue); + s << ";\n" << typeName << " *" << cppOut << " = &" << cppOutAux; + } + break; + case GeneratorArgument::Conversion::Pointer: { + s << " *" << cppOut; + if (!defaultValue.isEmpty()) { + const bool needsConstCast = !isNullPtr(defaultValue) + && type.indirections() == 1 && type.isConstant() + && type.referenceType() == NoReference; + s << " = "; + if (needsConstCast) + s << "const_cast<" << typeName << " *>("; + s << defaultValue; + if (needsConstCast) + s << ')'; + } + } + break; + case GeneratorArgument::Conversion::Default: + s << ' ' << cppOut; + if (isProtectedEnum && avoidProtectedHack()) { + s << " = "; + if (defaultValue.isEmpty()) + s << "{}"; + else + s << defaultValue; + } else if (type.isUserPrimitive() + || arg.type == GeneratorArgument::Type::Enum + || arg.type == GeneratorArgument::Type::Flags) { + writeMinimalConstructorExpression(s, api(), typeEntry, isPrimitive, defaultValue); + } else if ((!type.isContainer() || type.indirections() == 0) && !type.isSmartPointer()) { + writeMinimalConstructorExpression(s, api(), type, isPrimitive, defaultValue); + } + break; + } + s << ";\n"; + + QString pythonToCppFunc = pythonToCppConverterForArgumentName(pyIn); + + QString pythonToCppCall = pythonToCppFunc + u'(' + pyIn + u", &"_s + + cppOut + u')'; + if (arg.conversion != GeneratorArgument::Conversion::ValueOrPointer) { + // pythonToCppFunc may be 0 when less parameters are passed and + // the defaultValue takes effect. + if (!defaultValue.isEmpty()) + s << "if (" << pythonToCppFunc << ")\n" << indent; + s << pythonToCppCall << ";\n"; + if (!defaultValue.isEmpty()) + s << outdent; + return arg.indirections; + } + + // pythonToCppFunc may be 0 when less parameters are passed and + // the defaultValue takes effect. + if (!defaultValue.isEmpty()) + s << "if (" << pythonToCppFunc << ") {\n" << indent; + + s << "if (" << pythonToCppFunc << ".isValue())\n" + << indent << pythonToCppFunc << '(' << pyIn << ", &" << cppOutAux << ");\n" + << outdent << "else\n" << indent + << pythonToCppCall << ";\n" << outdent; + + if (defaultValue.isEmpty()) + s << '\n'; + else + s << "}\n" << outdent; + + return arg.indirections; +} + +static void addConversionRuleCodeSnippet(CodeSnipList &snippetList, QString &rule, + TypeSystem::Language /* conversionLanguage */, + TypeSystem::Language snippetLanguage, + const QString &outputName = QString(), + const QString &inputName = QString()) +{ + if (rule.isEmpty()) + return; + if (snippetLanguage == TypeSystem::TargetLangCode) { + rule.replace(u"%in"_s, inputName); + rule.replace(u"%out"_s, outputName + u"_out"_s); + } else { + rule.replace(u"%out"_s, outputName); + } + CodeSnip snip(snippetLanguage); + snip.position = (snippetLanguage == TypeSystem::NativeCode) ? TypeSystem::CodeSnipPositionAny : TypeSystem::CodeSnipPositionBeginning; + snip.addCode(rule); + snippetList << snip; +} + +void CppGenerator::writeConversionRule(TextStream &s, const AbstractMetaFunctionCPtr &func, + TypeSystem::Language language, bool usesPyArgs) const +{ + + CodeSnipList snippets; + const AbstractMetaArgumentList &arguments = func->arguments(); + for (const AbstractMetaArgument &arg : arguments) { + QString rule = func->conversionRule(language, arg.argumentIndex() + 1); + addConversionRuleCodeSnippet(snippets, rule, language, TypeSystem::TargetLangCode, + arg.name(), arg.name()); + } + writeCodeSnips(s, snippets, TypeSystem::CodeSnipPositionBeginning, TypeSystem::TargetLangCode, + func, usesPyArgs, nullptr); +} + +void CppGenerator::writeConversionRule(TextStream &s, const AbstractMetaFunctionCPtr &func, + TypeSystem::Language language, const QString &outputVar) const +{ + CodeSnipList snippets; + QString rule = func->conversionRule(language, 0); + addConversionRuleCodeSnippet(snippets, rule, language, language, outputVar); + writeCodeSnips(s, snippets, TypeSystem::CodeSnipPositionAny, language, + func, false /* uses PyArgs */, nullptr); +} + +void CppGenerator::writeNoneReturn(TextStream &s, const AbstractMetaFunctionCPtr &func, + bool thereIsReturnValue) +{ + if (thereIsReturnValue && (func->isVoid() || func->argumentRemoved(0)) + && !func->injectedCodeHasReturnValueAttribution()) { + s << PYTHON_RETURN_VAR << " = Py_None;\n" + << "Py_INCREF(Py_None);\n"; + } +} + +void CppGenerator::writeOverloadedFunctionDecisor(TextStream &s, + const OverloadData &overloadData, + ErrorReturn errorReturn) const +{ + s << "// Overloaded function decisor\n"; + const auto rfunc = overloadData.referenceFunction(); + const AbstractMetaFunctionCList &functionOverloads = overloadData.overloads(); + for (qsizetype i = 0; i < functionOverloads.size(); ++i) { + const auto func = functionOverloads.at(i); + s << "// " << i << ": "; + if (func->isStatic()) + s << "static "; + if (const auto &decl = func->declaringClass()) + s << decl->name() << "::"; + s << func->signatureComment() << '\n'; + } + writeOverloadedFunctionDecisorEngine(s, overloadData, &overloadData); + s << '\n'; + + // Ensure that the direct overload that called this reverse + // is called. + if (rfunc->isOperatorOverload() && !rfunc->isCallOperator()) { + s << "if (isReverse && overloadId == -1) {\n" << indent + << "Shiboken::Errors::setReverseOperatorNotImplemented();\n" + << "return {};\n" << outdent + << "}\n\n"; + } + + s << "// Function signature not found.\n" + << "if (overloadId == -1)\n" << indent + << "return " << returnErrorWrongArguments(overloadData, errorReturn) << ";\n\n" + << outdent; +} + +void CppGenerator::writeOverloadedFunctionDecisorEngine(TextStream &s, + const OverloadData &overloadData, + const OverloadDataRootNode *node) const +{ + bool hasDefaultCall = node->nextArgumentHasDefaultValue(); + auto referenceFunction = node->referenceFunction(); + + // If the next argument has not an argument with a default value, it is still possible + // that one of the overloads for the current overload data has its final occurrence here. + // If found, the final occurrence of a method is attributed to the referenceFunction + // variable to be used further on this method on the conditional that identifies default + // method calls. + if (!hasDefaultCall) { + for (const auto &func : node->overloads()) { + if (node->isFinalOccurrence(func)) { + referenceFunction = func; + hasDefaultCall = true; + break; + } + } + } + + const int maxArgs = overloadData.maxArgs(); + // Python constructors always receive multiple arguments. + const bool usePyArgs = overloadData.pythonFunctionWrapperUsesListOfArguments(); + + // Functions without arguments are identified right away. + if (maxArgs == 0) { + s << "overloadId = " << overloadData.functionNumber(referenceFunction) + << "; // " << referenceFunction->minimalSignature() << '\n'; + return; + + } + // To decide if a method call is possible at this point the current overload + // data object cannot be the head, since it is just an entry point, or a root, + // for the tree of arguments and it does not represent a valid method call. + if (!node->isRoot()) { + const bool isLastArgument = node->children().isEmpty(); + const bool signatureFound = node->overloads().size() == 1; + + // The current overload data describes the last argument of a signature, + // so the method can be identified right now. + if (isLastArgument || (signatureFound && !hasDefaultCall)) { + const auto func = node->referenceFunction(); + s << "overloadId = " << overloadData.functionNumber(func) + << "; // " << func->minimalSignature() << '\n'; + return; + } + } + + bool isFirst = true; + + // If the next argument has a default value the decisor can perform a method call; + // it just need to check if the number of arguments received from Python are equal + // to the number of parameters preceding the argument with the default value. + const OverloadDataList &children = node->children(); + if (hasDefaultCall) { + isFirst = false; + int numArgs = node->argPos() + 1; + s << "if (numArgs == " << numArgs << ") {\n" << indent; + auto func = referenceFunction; + for (const auto &child : children) { + const auto defValFunc = child->getFunctionWithDefaultValue(); + if (defValFunc) { + func = defValFunc; + break; + } + } + s << "overloadId = " << overloadData.functionNumber(func) + << "; // " << func->minimalSignature() << '\n' << outdent << '}'; + } + + for (auto child : children) { + bool signatureFound = child->overloads().size() == 1 + && !child->getFunctionWithDefaultValue() + && !child->findNextArgWithDefault(); + + const auto refFunc = child->referenceFunction(); + + QStringList typeChecks; + + QString pyArgName = (usePyArgs && maxArgs > 1) + ? pythonArgsAt(child->argPos()) + : PYTHON_ARG; + auto od = child; + int startArg = od->argPos(); + int sequenceArgCount = 0; + while (od && !od->argType().isVarargs()) { + const bool typeReplacedByPyObject = od->isTypeModified() + && od->modifiedArgType().name() == cPyObjectT; + if (!typeReplacedByPyObject) { + if (usePyArgs) + pyArgName = pythonArgsAt(od->argPos()); + StringStream tck(TextStream::Language::Cpp); + auto func = od->referenceFunction(); + + if (func->isConstructor() && func->arguments().size() == 1) { + AbstractMetaClassCPtr ownerClass = func->ownerClass(); + ComplexTypeEntryCPtr baseContainerType = ownerClass->typeEntry()->baseContainerType(); + if (baseContainerType && baseContainerType == func->arguments().constFirst().type().typeEntry() + && ownerClass->isCopyable()) { + tck << '!' << cpythonCheckFunction(ownerClass->typeEntry()) + << pyArgName << ")\n" << indent << "&& " << outdent; + } + } + writeTypeCheck(tck, od, pyArgName); + typeChecks << tck.toString(); + } + + sequenceArgCount++; + + if (od->children().isEmpty() + || od->nextArgumentHasDefaultValue() + || od->children().size() != 1 + || od->overloads().size() != od->children().constFirst()->overloads().size()) { + child = od; + od = nullptr; + } else { + od = od->children().constFirst(); + } + } + + if (usePyArgs && signatureFound) { + AbstractMetaArgumentList args = refFunc->arguments(); + const bool isVarargs = args.size() > 1 && args.constLast().type().isVarargs(); + int numArgs = args.size() - OverloadData::numberOfRemovedArguments(refFunc); + if (isVarargs) + --numArgs; + QString check = (isVarargs ? u"numArgs >= "_s : u"numArgs == "_s) + + QString::number(numArgs); + typeChecks.prepend(check); + } else if (usePyArgs && sequenceArgCount > 0) { + typeChecks.prepend(u"numArgs >= "_s + QString::number(startArg + sequenceArgCount)); + } else if (refFunc->isOperatorOverload() && !refFunc->isCallOperator()) { + QString check; + if (!refFunc->isReverseOperator()) + check.append(u'!'); + check.append(u"isReverse"_s); + typeChecks.prepend(check); + } + + if (isFirst) { + isFirst = false; + } else { + s << " else "; + } + s << "if ("; + if (typeChecks.isEmpty()) { + s << "true"; + } else { + s << indent << typeChecks.join(u"\n&& "_s) << outdent; + } + s << ") {\n" << indent; + writeOverloadedFunctionDecisorEngine(s, overloadData, child.get()); + s << outdent << '}'; + } + s << '\n'; +} + +void CppGenerator::writeFunctionCalls(TextStream &s, const OverloadData &overloadData, + const GeneratorContext &context, + ErrorReturn errorReturn) const +{ + const AbstractMetaFunctionCList &overloads = overloadData.overloads(); + s << "// Call function/method\n" + << (overloads.size() > 1 ? "switch (overloadId) " : "") << "{\n" << indent; + if (overloads.size() == 1) { + writeSingleFunctionCall(s, overloadData, overloads.constFirst(), context, + errorReturn); + } else { + for (qsizetype i = 0; i < overloads.size(); ++i) { + const auto func = overloads.at(i); + s << "case " << i << ": // " << func->signature() << "\n{\n" << indent; + writeSingleFunctionCall(s, overloadData, func, context, errorReturn); + s << "break;\n" << outdent << "}\n"; + } + } + s << outdent << "}\n"; +} + +static void writeDeprecationWarning(TextStream &s, + const GeneratorContext &context, + const AbstractMetaFunctionCPtr &func, + CppGenerator::ErrorReturn errorReturn) +{ + s << "Shiboken::Warnings::warnDeprecated(\""; + if (const auto cls = context.metaClass()) + s << cls->name() << "\", "; + // Check error in case "warning-as-error" is set. + s << '"' << func->signature().replace(u"::"_s, u"."_s) << "\");\n" + << "if (" << shibokenErrorsOccurred << ")\n" + << indent << errorReturn << outdent; +} + +void CppGenerator::writeSingleFunctionCall(TextStream &s, + const OverloadData &overloadData, + const AbstractMetaFunctionCPtr &func, + const GeneratorContext &context, + ErrorReturn errorReturn) const +{ + if (func->isDeprecated()) + writeDeprecationWarning(s, context, func, errorReturn); + + if (func->functionType() == AbstractMetaFunction::EmptyFunction) { + s << "Shiboken::Errors::setPrivateMethod(\"" + << func->signature().replace(u"::"_s, u"."_s) << "\");\n" + << errorReturn; + return; + } + + const bool usePyArgs = overloadData.pythonFunctionWrapperUsesListOfArguments(); + + // Handle named arguments. + writeNamedArgumentResolution(s, func, usePyArgs, overloadData, errorReturn); + + bool injectCodeCallsFunc = injectedCodeCallsCppFunction(context, func); + bool mayHaveUnunsedArguments = !func->isUserAdded() && func->hasInjectedCode() && injectCodeCallsFunc; + int removedArgs = 0; + + const auto argCount = func->arguments().size(); + QList<qsizetype> indirections(argCount, 0); + for (qsizetype argIdx = 0; argIdx < argCount; ++argIdx) { + const bool hasConversionRule = + func->hasConversionRule(TypeSystem::NativeCode, int(argIdx + 1)); + const AbstractMetaArgument &arg = func->arguments().at(argIdx); + if (arg.isModifiedRemoved()) { + if (!arg.defaultValueExpression().isEmpty()) { + const QString cppArgRemoved = CPP_ARG_REMOVED(argIdx); + s << getFullTypeName(arg.type()) << ' ' << cppArgRemoved; + s << " = " << arg.defaultValueExpression() << ";\n" + << sbkUnusedVariableCast(cppArgRemoved); + } else if (!injectCodeCallsFunc && !func->isUserAdded() && !hasConversionRule) { + // When an argument is removed from a method signature and no other means of calling + // the method are provided (as with code injection) the generator must abort. + QString m; + QTextStream(&m) << "No way to call '" << func->ownerClass()->name() + << "::" << func->signature() + << "' with the modifications described in the type system."; + throw Exception(m); + } + removedArgs++; + continue; + } + if (hasConversionRule) + continue; + if (mayHaveUnunsedArguments && !func->injectedCodeUsesArgument(argIdx)) + continue; + auto argType = getArgumentType(func, argIdx); + int argPos = argIdx - removedArgs; + QString pyArgName = usePyArgs ? pythonArgsAt(argPos) : PYTHON_ARG; + indirections[argIdx] = + writeArgumentConversion(s, argType, CPP_ARG_N(argPos), pyArgName, errorReturn, + func->implementingClass(), arg.defaultValueExpression(), + func->isUserAdded()); + } + + s << '\n'; + + int numRemovedArgs = OverloadData::numberOfRemovedArguments(func); + + s << "if (Shiboken::Errors::occurred() == nullptr) {\n" << indent; + writeMethodCall(s, func, context, + overloadData.pythonFunctionWrapperUsesListOfArguments(), + func->arguments().size() - numRemovedArgs, indirections, errorReturn); + + if (!func->isConstructor()) + writeNoneReturn(s, func, overloadData.hasNonVoidReturnType()); + s << outdent << "}\n"; +} + +QString CppGenerator::cppToPythonFunctionName(const QString &sourceTypeName, QString targetTypeName) +{ + if (targetTypeName.isEmpty()) + targetTypeName = sourceTypeName; + return sourceTypeName + u"_CppToPython_"_s + targetTypeName; +} + +QString CppGenerator::pythonToCppFunctionName(const QString &sourceTypeName, const QString &targetTypeName) +{ + return sourceTypeName + u"_PythonToCpp_"_s + targetTypeName; +} +QString CppGenerator::pythonToCppFunctionName(const AbstractMetaType &sourceType, const AbstractMetaType &targetType) +{ + return pythonToCppFunctionName(fixedCppTypeName(sourceType), fixedCppTypeName(targetType)); +} +QString CppGenerator::pythonToCppFunctionName(const TargetToNativeConversion &toNative, + const TypeEntryCPtr &targetType) +{ + return pythonToCppFunctionName(fixedCppTypeName(toNative), fixedCppTypeName(targetType)); +} + +QString CppGenerator::convertibleToCppFunctionName(const QString &sourceTypeName, const QString &targetTypeName) +{ + return u"is_"_s + sourceTypeName + u"_PythonToCpp_"_s + + targetTypeName + u"_Convertible"_s; +} +QString CppGenerator::convertibleToCppFunctionName(const AbstractMetaType &sourceType, const AbstractMetaType &targetType) +{ + return convertibleToCppFunctionName(fixedCppTypeName(sourceType), fixedCppTypeName(targetType)); +} +QString CppGenerator::convertibleToCppFunctionName(const TargetToNativeConversion &toNative, + const TypeEntryCPtr &targetType) +{ + return convertibleToCppFunctionName(fixedCppTypeName(toNative), fixedCppTypeName(targetType)); +} + +void CppGenerator::writeCppToPythonFunction(TextStream &s, const QString &code, const QString &sourceTypeName, + QString targetTypeName) const +{ + + QString prettyCode = code; + const QString funcName = cppToPythonFunctionName(sourceTypeName, targetTypeName); + processCodeSnip(prettyCode, funcName); + + s << "static PyObject *" << funcName + << "(const void *cppIn)\n{\n" << indent << prettyCode + << ensureEndl << outdent << "}\n"; +} + +static QString writeCppInRef(const QString &typeName, bool constRef) +{ + QString result; + QTextStream str(&result); + if (constRef) + str << "const "; + str << "auto &cppInRef = *reinterpret_cast<"; + if (constRef) + str << "const "; + str << typeName << " *>(" + << (constRef ? "cppIn" : "const_cast<void *>(cppIn)") << ");"; + return result; +} + +static void replaceCppToPythonVariables(QString &code, const QString &typeName, + bool constRef = false) +{ + CodeSnipAbstract::prependCode(&code, writeCppInRef(typeName, constRef)); + code.replace(u"%INTYPE"_s, typeName); + code.replace(u"%OUTTYPE"_s, u"PyObject *"_s); + code.replace(u"%in"_s, u"cppInRef"_s); + code.replace(u"%out"_s, u"pyOut"_s); +} + +void CppGenerator::writeCppToPythonFunction(TextStream &s, + const CustomConversionPtr &customConversion) const +{ + QString code = customConversion->nativeToTargetConversion(); + auto ownerType = customConversion->ownerType(); + const bool constRef = !ownerType->isPrimitive(); // PyCapsule needs a non-const ref + replaceCppToPythonVariables(code, getFullTypeName(ownerType), constRef); + writeCppToPythonFunction(s, code, fixedCppTypeName(customConversion->ownerType())); +} + +QString CppGenerator::containerNativeToTargetTypeName(const ContainerTypeEntryCPtr &type) +{ + QString result = type->targetLangApiName(); + if (result != cPyObjectT) { + result = containerCpythonBaseName(type); + if (result == cPySequenceT) + result = cPyListT; + } + return result; +} + +void CppGenerator::writeCppToPythonFunction(TextStream &s, const AbstractMetaType &containerType) const +{ + Q_ASSERT(containerType.typeEntry()->isContainer()); + auto cte = std::static_pointer_cast<const ContainerTypeEntry>(containerType.typeEntry()); + if (!cte->hasCustomConversion()) { + QString m; + QTextStream(&m) << "Can't write the C++ to Python conversion function for container type '" + << containerType.typeEntry()->qualifiedCppName() + << "' - no conversion rule was defined for it in the type system."; + throw Exception(m); + } + const auto customConversion = cte->customConversion(); + QString code = customConversion->nativeToTargetConversion(); + for (qsizetype i = 0; i < containerType.instantiations().size(); ++i) { + const AbstractMetaType &type = containerType.instantiations().at(i); + QString typeName = getFullTypeName(type); + if (type.isConstant()) + typeName = u"const "_s + typeName; + code.replace(u"%INTYPE_"_s + QString::number(i), typeName); + } + replaceCppToPythonVariables(code, getFullTypeNameWithoutModifiers(containerType), true); + processCodeSnip(code, containerType.typeEntry()->qualifiedCppName()); + writeCppToPythonFunction(s, code, fixedCppTypeName(containerType), + containerNativeToTargetTypeName(cte)); +} + +void CppGenerator::writePythonToCppFunction(TextStream &s, const QString &code, const QString &sourceTypeName, + const QString &targetTypeName) const +{ + QString prettyCode = code; + const QString funcName = pythonToCppFunctionName(sourceTypeName, targetTypeName); + processCodeSnip(prettyCode, funcName); + s << "static void " << funcName + << "(PyObject *pyIn, void *cppOut)\n{\n" << indent << prettyCode + << ensureEndl << outdent << "}\n"; +} + +void CppGenerator::writeIsPythonConvertibleToCppFunction(TextStream &s, + const QString &sourceTypeName, + const QString &targetTypeName, + const QString &condition, + QString pythonToCppFuncName, + bool acceptNoneAsCppNull) +{ + if (pythonToCppFuncName.isEmpty()) + pythonToCppFuncName = pythonToCppFunctionName(sourceTypeName, targetTypeName); + + s << "static PythonToCppFunc " << convertibleToCppFunctionName(sourceTypeName, targetTypeName); + s << "(PyObject *pyIn)\n{\n" << indent; + if (acceptNoneAsCppNull) { + s << "if (pyIn == Py_None)\n" << indent + << "return Shiboken::Conversions::nonePythonToCppNullPtr;\n" << outdent; + } else { + if (!condition.contains(u"pyIn")) + s << sbkUnusedVariableCast("pyIn"); + } + s << "if (" << condition << ")\n" << indent + << "return " << pythonToCppFuncName << ";\n" << outdent + << "return {};\n" << outdent << "}\n"; +} + +void CppGenerator::writePythonToCppConversionFunctions(TextStream &s, + const AbstractMetaType &sourceType, + const AbstractMetaType &targetType, + QString typeCheck, + QString conversion, + const QString &preConversion) const +{ + QString sourcePyType = cpythonTypeNameExt(sourceType); + + // Python to C++ conversion function. + StringStream c(TextStream::Language::Cpp); + if (conversion.isEmpty()) + conversion = u'*' + cpythonWrapperCPtr(sourceType, u"pyIn"_s); + if (!preConversion.isEmpty()) + c << preConversion << '\n'; + const QString fullTypeName = targetType.isSmartPointer() + ? targetType.cppSignature() + : getFullTypeName(targetType.typeEntry()); + c << "*reinterpret_cast<" << fullTypeName << " *>(cppOut) = " + << fullTypeName << '(' + << (sourceType.isUniquePointer() ? stdMove(conversion) : conversion) + << ");"; + QString sourceTypeName = fixedCppTypeName(sourceType); + QString targetTypeName = fixedCppTypeName(targetType); + writePythonToCppFunction(s, c.toString(), sourceTypeName, targetTypeName); + + // Python to C++ convertible check function. + if (typeCheck.isEmpty()) + typeCheck = u"PyObject_TypeCheck(pyIn, "_s + sourcePyType + u')'; + writeIsPythonConvertibleToCppFunction(s, sourceTypeName, targetTypeName, typeCheck); + s << '\n'; +} + +void CppGenerator::writePythonToCppConversionFunctions(TextStream &s, + const TargetToNativeConversion &toNative, + const TypeEntryCPtr &targetType) const +{ + // Python to C++ conversion function. + QString code = toNative.conversion(); + QString inType; + if (toNative.sourceType()) + inType = cpythonTypeNameExt(toNative.sourceType()); + else + inType = u'(' + toNative.sourceTypeName() + u"_TypeF())"_s; + code.replace(u"%INTYPE"_s, inType); + code.replace(u"%OUTTYPE"_s, targetType->qualifiedCppName()); + code.replace(u"%in"_s, u"pyIn"_s); + code.replace(u"%out"_s, + u"*reinterpret_cast<"_s + getFullTypeName(targetType) + u" *>(cppOut)"_s); + + QString sourceTypeName = fixedCppTypeName(toNative); + QString targetTypeName = fixedCppTypeName(targetType); + writePythonToCppFunction(s, code, sourceTypeName, targetTypeName); + + // Python to C++ convertible check function. + QString typeCheck = toNative.sourceTypeCheck(); + if (typeCheck.isEmpty()) { + QString pyTypeName = toNative.sourceTypeName(); + if (pyTypeName == u"Py_None" || pyTypeName == u"PyNone") + typeCheck = u"%in == Py_None"_s; + else if (pyTypeName == u"SbkObject") + typeCheck = u"Shiboken::Object::checkType(%in)"_s; + } + if (typeCheck.isEmpty()) { + if (!toNative.sourceType() || toNative.sourceType()->isPrimitive()) { + QString m; + QTextStream(&m) << "User added implicit conversion for C++ type '" << targetType->qualifiedCppName() + << "' must provide either an input type check function or a non primitive type entry."; + throw Exception(m); + } + typeCheck = u"PyObject_TypeCheck(%in, "_s + + cpythonTypeNameExt(toNative.sourceType()) + u')'; + } + typeCheck.replace(u"%in"_s, u"pyIn"_s); + processCodeSnip(typeCheck, targetType->qualifiedCppName()); + writeIsPythonConvertibleToCppFunction(s, sourceTypeName, targetTypeName, typeCheck); +} + +void CppGenerator::writePythonToCppConversionFunctions(TextStream &s, const AbstractMetaType &containerType) const +{ + Q_ASSERT(containerType.typeEntry()->isContainer()); + const auto cte = std::static_pointer_cast<const ContainerTypeEntry>(containerType.typeEntry()); + const auto customConversion = cte->customConversion(); + for (const auto &conv : customConversion->targetToNativeConversions()) + writePythonToCppConversionFunction(s, containerType, conv); +} + +void CppGenerator::writePythonToCppConversionFunction(TextStream &s, + const AbstractMetaType &containerType, + const TargetToNativeConversion &conv) const +{ + // Python to C++ conversion function. + QString cppTypeName = getFullTypeNameWithoutModifiers(containerType); + QString code = conv.conversion(); + const QString line = u"auto &cppOutRef = *reinterpret_cast<"_s + + cppTypeName + u" *>(cppOut);"_s; + CodeSnipAbstract::prependCode(&code, line); + for (qsizetype i = 0; i < containerType.instantiations().size(); ++i) { + const AbstractMetaType &type = containerType.instantiations().at(i); + QString typeName = getFullTypeName(type); + // Containers of opaque containers are not handled here. + const auto generatorArg = GeneratorArgument::fromMetaType(type); + if (generatorArg.indirections > 0 && !type.generateOpaqueContainer()) { + for (int pos = 0; ; ) { + const QRegularExpressionMatch match = convertToCppRegEx().match(code, pos); + if (!match.hasMatch()) + break; + pos = match.capturedEnd(); + const QString varName = match.captured(1); + QString rightCode = code.mid(pos); + rightCode.replace(varName, u'*' + varName); + code.replace(pos, code.size() - pos, rightCode); + } + typeName.append(u" *"_s); + } + code.replace(u"%OUTTYPE_"_s + QString::number(i), typeName); + } + code.replace(u"%OUTTYPE"_s, cppTypeName); + code.replace(u"%in"_s, u"pyIn"_s); + code.replace(u"%out"_s, u"cppOutRef"_s); + QString typeName = fixedCppTypeName(containerType); + const QString &sourceTypeName = conv.sourceTypeName(); + writePythonToCppFunction(s, code, sourceTypeName, typeName); + + // Python to C++ convertible check function. + QString typeCheck = cpythonCheckFunction(containerType); + if (typeCheck.isEmpty()) + typeCheck = u"false"_s; + else + typeCheck = typeCheck + u"pyIn)"_s; + writeIsPythonConvertibleToCppFunction(s, sourceTypeName, typeName, typeCheck); + s << '\n'; +} + +static void writeSetConverterFunction(TextStream &s, + const char *function, + const QString &converterVar, + const QString &pythonToCppFunc, + const QString &isConvertibleFunc) +{ + s << "Shiboken::Conversions::" << function << '(' << converterVar << ',' << '\n' + << indent << pythonToCppFunc << ',' << '\n' << isConvertibleFunc + << outdent << ");\n"; +} + +void CppGenerator::writeAddPythonToCppConversion(TextStream &s, const QString &converterVar, + const QString &pythonToCppFunc, + const QString &isConvertibleFunc) +{ + writeSetConverterFunction(s, "addPythonToCppValueConversion", + converterVar, pythonToCppFunc, isConvertibleFunc); +} + +void CppGenerator::writeSetPythonToCppPointerConversion(TextStream &s, + const QString &converterVar, + const QString &pythonToCppFunc, + const QString &isConvertibleFunc) +{ + writeSetConverterFunction(s, "setPythonToCppPointerFunctions", + converterVar, pythonToCppFunc, isConvertibleFunc); +} + +// PYSIDE-1986: Some QObject derived classes, (QVBoxLayout) do not have default +// arguments, which breaks setting properties by named arguments. Force the +// handling code to be generated nevertheless for applicable widget classes, +// so that the mechanism of falling through to the error handling to set +// the properties works nevertheless. +static bool forceQObjectNamedArguments(const AbstractMetaFunctionCPtr &func) +{ + if (func->functionType() != AbstractMetaFunction::ConstructorFunction) + return false; + const auto owner = func->ownerClass(); + Q_ASSERT(owner); + if (!isQObject(owner)) + return false; + const QString &name = owner->name(); + return name == u"QVBoxLayout" || name == u"QHBoxLayout" + || name == u"QSplitterHandle" || name == u"QSizeGrip"; +} + +void CppGenerator::writeNamedArgumentResolution(TextStream &s, + const AbstractMetaFunctionCPtr &func, + bool usePyArgs, + const OverloadData &overloadData, + ErrorReturn errorReturn) +{ + const AbstractMetaArgumentList &args = OverloadData::getArgumentsWithDefaultValues(func); + const bool hasDefaultArguments = !args.isEmpty(); + const bool force = !hasDefaultArguments && usePySideExtensions() + && forceQObjectNamedArguments(func); + if (!hasDefaultArguments && !force) { + if (overloadData.hasArgumentWithDefaultValue()) { + // PySide-535: Allow for empty dict instead of nullptr in PyPy + s << "if (kwds != nullptr && PyDict_Size(kwds) > 0) {\n" << indent + << "errInfo.reset(kwds);\n" + << "Py_INCREF(errInfo.object());\n" + << "return " << returnErrorWrongArguments(overloadData, errorReturn) << ";\n" + << outdent << "}\n"; + } + return; + } + + // PySide-535: Allow for empty dict instead of nullptr in PyPy + s << "if (kwds && PyDict_Size(kwds) > 0) {\n" << indent; + if (!force) + s << "PyObject *value{};\n"; + s << "Shiboken::AutoDecRef kwds_dup(PyDict_Copy(kwds));\n"; + for (const AbstractMetaArgument &arg : args) { + const int pyArgIndex = arg.argumentIndex() + - OverloadData::numberOfRemovedArguments(func, arg.argumentIndex()); + QString pyArgName = usePyArgs ? pythonArgsAt(pyArgIndex) + : PYTHON_ARG; + QString pyKeyName = u"key_"_s + arg.name(); + s << "static PyObject *const " << pyKeyName + << " = Shiboken::String::createStaticString(\"" << arg.name() << "\");\n" + << "if (PyDict_Contains(kwds, " << pyKeyName << ") != 0) {\n" << indent + << "value = PyDict_GetItem(kwds, " << pyKeyName << ");\n" + << "if (value != nullptr && " << pyArgName << " != nullptr ) {\n" + << indent << "errInfo.reset(" << pyKeyName << ");\n" + << "Py_INCREF(errInfo.object());\n" + << "return " << returnErrorWrongArguments(overloadData, errorReturn) << ";\n" + << outdent << "}\nif (value != nullptr) {\n" << indent + << pyArgName << " = value;\nif (!"; + const auto &type = arg.modifiedType(); + writeTypeCheck(s, type, pyArgName, isNumber(type.typeEntry()), {}); + s << ")\n" << indent + << "return " << returnErrorWrongArguments(overloadData, errorReturn) << ";\n" + << outdent << outdent + << "}\nPyDict_DelItem(kwds_dup, " << pyKeyName << ");\n" + << outdent << "}\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 << "if (PyDict_Size(kwds_dup) > 0) {\n" << indent + << "errInfo.reset(kwds_dup.release());\n"; + if (!(func->isConstructor() && isQObject(func->ownerClass()))) + s << "return " << returnErrorWrongArguments(overloadData, errorReturn) << ";\n"; + else + s << "// fall through to handle extra keyword signals and properties\n"; + s << outdent << "}\n" + << outdent << "}\n"; +} + +QString CppGenerator::argumentNameFromIndex(const ApiExtractorResult &api, + const AbstractMetaFunctionCPtr &func, int argIndex) +{ + switch (argIndex) { + case -1: + return PYTHON_SELF_VAR; + case 0: + return PYTHON_RETURN_VAR; + case 1: { // Single argument? + OverloadData data(getFunctionGroups(func->implementingClass()).value(func->name()), api); + if (!data.pythonFunctionWrapperUsesListOfArguments()) + return PYTHON_ARG; + break; + } + } + return pythonArgsAt(argIndex - 1); +} + +AbstractMetaClassCPtr +CppGenerator::argumentClassFromIndex(const ApiExtractorResult &api, + const AbstractMetaFunctionCPtr &func, int argIndex) +{ + if (argIndex == -1) + return func->implementingClass(); + + AbstractMetaType type; + if (argIndex == 0) { + type = func->type(); + } else { + const int arg = argIndex - 1; + const int realIndex = arg - OverloadData::numberOfRemovedArguments(func, arg); + type = func->arguments().at(realIndex).type(); + } + + if (type.typeEntry()->isContainer()) { + // only support containers with 1 type + if (type.instantiations().size() == 1) + type = type.instantiations().constFirst(); + } + + auto te = type.typeEntry(); + if (type.isVoid() || !te->isComplex()) + throw Exception(msgInvalidArgumentModification(func, argIndex)); + const auto result = AbstractMetaClass::findClass(api.classes(), te); + if (!result) + throw Exception(msgClassNotFound(te)); + return result; +} + +const char tryBlock[] = R"( +PyObject *errorType{}; +PyObject *errorString{}; +try { +)"; + +const char defaultExceptionHandling[] = R"(} catch (const std::exception &e) { + errorType = PyExc_RuntimeError; + errorString = Shiboken::String::fromCString(e.what()); +} catch (...) { + errorType = PyExc_RuntimeError; + errorString = Shiboken::Messages::unknownException(); +} +)"; + +const char propagateException[] = R"( +if (errorType != nullptr) + PyErr_SetObject(errorType, errorString); +)"; + +static QString explicitConversion(const QString &v, const AbstractMetaType &t) +{ + return t.plainType().cppSignature() + u'(' + v + u')'; +} + +void CppGenerator::writeMethodCall(TextStream &s, const AbstractMetaFunctionCPtr &func, + const GeneratorContext &context, bool usesPyArgs, + int maxArgs, + const QList<qsizetype> &argumentIndirections, + ErrorReturn errorReturn) const +{ + s << "// " << func->minimalSignature() << (func->isReverseOperator() ? " [reverse operator]": "") << '\n'; + if (func->isConstructor()) { + const CodeSnipList &snips = func->injectedCodeSnips(); + for (const CodeSnip &cs : snips) { + if (cs.position == TypeSystem::CodeSnipPositionEnd) { + auto klass = func->ownerClass(); + s << "overloadId = " + << klass->functions().indexOf(func) + << ";\n"; + break; + } + } + } + + if (func->isAbstract()) { + s << "if (Shiboken::Object::hasCppWrapper(reinterpret_cast<SbkObject *>(self))) {\n" + << indent << "Shiboken::Errors::setPureVirtualMethodError(\"" + << func->ownerClass()->name() << '.' << func->name() << "\");\n" + << errorReturn << outdent << "}\n"; + } + + // Used to provide contextual information to custom code writer function. + const AbstractMetaArgument *lastArg = nullptr; + + CodeSnipList snips; + if (func->hasInjectedCode()) { + snips = func->injectedCodeSnips(); + + // Find the last argument available in the method call to provide + // the injected code writer with information to avoid invalid replacements + // on the %# variable. + if (maxArgs > 0 && maxArgs < func->arguments().size() - OverloadData::numberOfRemovedArguments(func)) { + int removedArgs = 0; + for (int i = 0; i < maxArgs + removedArgs; i++) { + if (func->arguments().at(i).isModifiedRemoved()) + removedArgs++; + } + } else if (maxArgs != 0 && !func->arguments().isEmpty()) { + lastArg = &func->arguments().constLast(); + } + + writeCodeSnips(s, snips, TypeSystem::CodeSnipPositionBeginning, TypeSystem::TargetLangCode, func, + usesPyArgs, lastArg); + } + + writeConversionRule(s, func, TypeSystem::NativeCode, usesPyArgs); + + bool generateExceptionHandling = false; + + if (!func->isUserAdded()) { + QStringList userArgs; + if (func->functionType() != AbstractMetaFunction::CopyConstructorFunction) { + int removedArgs = 0; + for (int i = 0; i < maxArgs + removedArgs; i++) { + const AbstractMetaArgument &arg = func->arguments().at(i); + const bool hasConversionRule = + func->hasConversionRule(TypeSystem::NativeCode, arg.argumentIndex() + 1); + if (arg.isModifiedRemoved()) { + // If some argument with default value is removed from a + // method signature, the said value must be explicitly + // added to the method call. + removedArgs++; + + // If have conversion rules I will use this for removed args + if (hasConversionRule) + userArgs << arg.name() + CONV_RULE_OUT_VAR_SUFFIX; + else if (!arg.defaultValueExpression().isEmpty()) + userArgs.append(CPP_ARG_REMOVED(i)); + } else { + if (hasConversionRule) { + userArgs.append(arg.name() + CONV_RULE_OUT_VAR_SUFFIX); + } else { + const int idx = arg.argumentIndex() - removedArgs; + const auto deRef = argumentIndirections.at(i); + QString argName = AbstractMetaType::dereferencePrefix(deRef) + + CPP_ARG_N(idx); + userArgs.append(argName); + } + } + // "Pass unique ptr by value" pattern: Apply std::move() + auto type = arg.type(); + if (type.useStdMove()) + userArgs.last() = stdMove(userArgs.constLast()); + else if (type.viewOn() != nullptr) + userArgs.last() = explicitConversion(userArgs.constLast(), type); + } + + // If any argument's default value was modified the method must be called + // with this new value whenever the user doesn't pass an explicit value to it. + // Also, any unmodified default value coming after the last user specified + // argument and before the modified argument must be explicitly stated. + QStringList otherArgs; + bool otherArgsModified = false; + bool argsClear = true; + for (auto i = func->arguments().size() - 1; i >= maxArgs + removedArgs; i--) { + const AbstractMetaArgument &arg = func->arguments().at(i); + const bool defValModified = arg.hasModifiedDefaultValueExpression(); + const bool hasConversionRule = + func->hasConversionRule(TypeSystem::NativeCode, arg.argumentIndex() + 1); + if (argsClear && !defValModified && !hasConversionRule) + continue; + argsClear = false; + otherArgsModified |= defValModified || hasConversionRule || arg.isModifiedRemoved(); + if (hasConversionRule) + otherArgs.prepend(arg.name() + CONV_RULE_OUT_VAR_SUFFIX); + else + otherArgs.prepend(CPP_ARG_REMOVED(i)); + } + if (otherArgsModified) + userArgs << otherArgs; + } + + bool isCtor = false; + StringStream mc(TextStream::Language::Cpp); + + StringStream uva(TextStream::Language::Cpp); + if (func->isOperatorOverload() && !func->isCallOperator()) { + QString firstArg(u'('); + if (!func->isPointerOperator()) // no de-reference operator + firstArg += u'*'; + firstArg += CPP_SELF_VAR; + firstArg += u')'; + QString secondArg = CPP_ARG0; + if (!func->isUnaryOperator()) + AbstractMetaType::applyDereference(&secondArg, argumentIndirections.at(0)); + + if (func->isUnaryOperator()) + std::swap(firstArg, secondArg); + + QString op = func->originalName(); + op.remove(0, int(std::strlen("operator"))); + + if (func->isBinaryOperator()) { + if (func->isReverseOperator()) + std::swap(firstArg, secondArg); + + // Emulate operator+=/-= (__iadd__, __isub__) by using ++/-- + if (((op == u"++") || (op == u"--")) && !func->isReverseOperator()) { + s << "\nfor (int i = 0; i < " << secondArg + << "; ++i, " << op << firstArg << ");\n"; + mc << firstArg; + } else { + mc << firstArg << ' ' << op << ' ' << secondArg; + } + } else { + mc << op << ' ' << secondArg; + } + } else if (!injectedCodeCallsCppFunction(context, func)) { + if (func->isConstructor()) { + isCtor = true; + const auto owner = func->ownerClass(); + Q_ASSERT(owner == context.metaClass()); + if (func->functionType() == AbstractMetaFunction::CopyConstructorFunction + && maxArgs == 1) { + mc << "new " << globalScopePrefix(context) << context.effectiveClassName() + << "(*" << CPP_ARG0 << ')'; + } else { + const QString ctorCall = context.effectiveClassName() + u'(' + + userArgs.join(u", "_s) + u')'; + if (usePySideExtensions() && isQObject(owner)) { + s << "void *addr = PySide::nextQObjectMemoryAddr();\n"; + uva << "if (addr != nullptr) {\n" << indent + << "cptr = new (addr) " << globalScopePrefix(context) << ctorCall + << ";\nPySide::setNextQObjectMemoryAddr(nullptr);\n" << outdent + << "} else {\n" << indent + << "cptr = new " << globalScopePrefix(context) << ctorCall << ";\n" + << outdent << "}\n"; + } else { + mc << "new " << globalScopePrefix(context) << ctorCall; + } + } + } else { + QString methodCallClassName; + if (context.forSmartPointer()) + methodCallClassName = context.preciseType().cppSignature(); + else if (func->ownerClass()) + methodCallClassName = func->ownerClass()->qualifiedCppName(); + + if (auto ownerClass = func->ownerClass()) { + const bool hasWrapper = shouldGenerateCppWrapper(ownerClass); + if (!avoidProtectedHack() || !func->isProtected() || !hasWrapper) { + if (func->isStatic()) { + mc << m_gsp << methodCallClassName << "::"; + } else { + const QString cppSelfVar = CPP_SELF_VAR; + const QString selfVarCast = func->ownerClass() == func->implementingClass() + ? cppSelfVar + : u"reinterpret_cast<"_s + methodCallClassName + + u" *>("_s + cppSelfVar + u')'; + if (func->isConstant()) { + if (avoidProtectedHack()) { + mc << "const_cast<const " << globalScopePrefix(context); + if (ownerClass->cppWrapper().testFlag(AbstractMetaClass::CppProtectedHackWrapper)) { + // PYSIDE-500: Need a special wrapper cast when inherited + const QString selfWrapCast = ownerClass == func->implementingClass() + ? cppSelfVar + : u"reinterpret_cast<"_s + wrapperName(ownerClass) + + u" *>("_s + cppSelfVar + u')'; + mc << wrapperName(ownerClass); + mc << " *>(" << selfWrapCast << ")->"; + } + else { + mc << methodCallClassName; + mc << " *>(" << selfVarCast << ")->"; + } + } else { + mc << "const_cast<const " << m_gsp << methodCallClassName; + mc << " *>(" << selfVarCast << ")->"; + } + } else { + mc << selfVarCast << "->"; + } + } + + if (!func->isAbstract() && func->isVirtual()) + mc << "::%CLASS_NAME::"; + + mc << func->originalName(); + } else { + if (!func->isStatic()) { + const bool directInheritance = context.metaClass() == ownerClass; + mc << (directInheritance ? "static_cast" : "reinterpret_cast") + << '<' << wrapperName(ownerClass) << " *>(" + << CPP_SELF_VAR << ")->"; + } + + if (!func->isAbstract()) + mc << (func->isProtected() ? wrapperName(func->ownerClass()) : + m_gsp + methodCallClassName) << "::"; + mc << func->originalName() << "_protected"; + } + } else { + mc << func->originalName(); + } + mc << '(' << userArgs.join(u", "_s) << ')'; + if (!func->isAbstract() && func->isVirtual()) { + if (!avoidProtectedHack() || !func->isProtected()) { + QString virtualCall = mc; + QString normalCall = virtualCall; + virtualCall.replace(u"%CLASS_NAME"_s, + methodCallClassName); + normalCall.remove(u"::%CLASS_NAME::"_s); + mc.clear(); + mc << "Shiboken::Object::hasCppWrapper(reinterpret_cast<SbkObject *>(self))\n" + << " ? " << virtualCall << '\n' + << " : " << normalCall; + } + } + } + } + + if (!injectedCodeCallsCppFunction(context, func)) { + const bool allowThread = func->allowThread(); + generateExceptionHandling = func->generateExceptionHandling(); + if (generateExceptionHandling) { + s << tryBlock << indent; + if (allowThread) { + s << "Shiboken::ThreadStateSaver threadSaver;\n" + << "threadSaver.save();\n"; + } + } else if (allowThread) { + s << BEGIN_ALLOW_THREADS << '\n'; + } + if (isCtor) { + if (uva.size() > 0) + s << uva.toString() << '\n'; + else + s << "cptr = " << mc.toString() << ";\n"; + } else if (!func->isVoid() && !func->isInplaceOperator()) { + bool writeReturnType = true; + if (avoidProtectedHack()) { + auto metaEnum = api().findAbstractMetaEnum(func->type().typeEntry()); + if (metaEnum.has_value()) { + QString enumName; + if (metaEnum->isProtected()) { + enumName = context.wrapperName() + u"::"_s + + metaEnum.value().name(); + } else { + enumName = func->type().cppSignature(); + } + const QString methodCall = enumName + u'(' + + mc.toString() + u')'; + mc.clear(); + mc << methodCall; + s << enumName; + writeReturnType = false; + } + } + QString methodCall = mc.toString(); + if (writeReturnType) { + s << func->type().cppSignature(); + if (func->type().isObjectTypeUsedAsValueType()) { + s << '*'; + methodCall = u"new "_s + + func->type().typeEntry()->qualifiedCppName() + + u'(' + mc.toString() + u')'; + } + } + s << " " << CPP_RETURN_VAR << " = " << methodCall << ";\n"; + } else { + s << mc.toString() << ";\n"; + } + + if (allowThread) { + s << (generateExceptionHandling + ? u"threadSaver.restore();"_s : END_ALLOW_THREADS) << '\n'; + } + + // Convert result + const auto funcType = func->type(); + if (func->hasConversionRule(TypeSystem::TargetLangCode, 0)) { + writeConversionRule(s, func, TypeSystem::TargetLangCode, + PYTHON_RETURN_VAR); + } else if (!isCtor && !func->isInplaceOperator() && !func->isVoid() + && !func->injectedCodeHasReturnValueAttribution(TypeSystem::TargetLangCode)) { + if (func->type().isObjectTypeUsedAsValueType()) { + s << PYTHON_RETURN_VAR << " = Shiboken::Object::newObject(" + << cpythonTypeNameExt(func->type().typeEntry()) + << ", " << CPP_RETURN_VAR << ", true, true)"; + } else if (func->generateOpaqueContainerReturn()) { + const QString creationFunc = opaqueContainerCreationFunc(funcType); + writeOpaqueContainerCreationFuncDecl(s, creationFunc, funcType); + s << PYTHON_RETURN_VAR << " = " << creationFunc + << "(&" << CPP_RETURN_VAR << ");\n"; + } else { + s << PYTHON_RETURN_VAR << " = "; + writeToPythonConversion(s, funcType, func->ownerClass(), + CPP_RETURN_VAR); + } + s << ";\n"; + } + + if (generateExceptionHandling) { // "catch" code + s << outdent << defaultExceptionHandling; + } + } // !injected code calls C++ function + } // !userAdded + + if (func->hasInjectedCode() && !func->isConstructor()) + writeCodeSnips(s, snips, TypeSystem::CodeSnipPositionEnd, + TypeSystem::TargetLangCode, func, usesPyArgs, lastArg); + + bool hasReturnPolicy = false; + + // Ownership transference between C++ and Python. + QList<ArgumentModification> ownership_mods; + // Python object reference management. + QList<ArgumentModification> refcount_mods; + for (const auto &func_mod : func->modifications()) { + for (const ArgumentModification &arg_mod : func_mod.argument_mods()) { + if (arg_mod.targetOwnerShip() != TypeSystem::UnspecifiedOwnership) + ownership_mods.append(arg_mod); + else if (!arg_mod.referenceCounts().isEmpty()) + refcount_mods.append(arg_mod); + } + } + + // If there's already a setParent(return, me), don't use the return heuristic! + if (func->argumentOwner(func->ownerClass(), -1).index == 0) + hasReturnPolicy = true; + + if (!ownership_mods.isEmpty()) { + s << '\n' << "// Ownership transferences.\n"; + for (const ArgumentModification &arg_mod : std::as_const(ownership_mods)) { + const int argIndex = arg_mod.index(); + const QString pyArgName = argumentNameFromIndex(api(), func, argIndex); + + if (argIndex == 0 || arg_mod.owner().index == 0) + hasReturnPolicy = true; + + // The default ownership does nothing. This is useful to avoid automatic heuristically + // based generation of code defining parenting. + const auto ownership = arg_mod.targetOwnerShip(); + if (ownership == TypeSystem::DefaultOwnership) + continue; + + s << "Shiboken::Object::"; + if (ownership == TypeSystem::TargetLangOwnership) { + s << "getOwnership(" << pyArgName << ");"; + } else if (auto ac = argumentClassFromIndex(api(), func, argIndex); + ac && ac->hasVirtualDestructor()) { + s << "releaseOwnership(" << pyArgName << ");"; + } else { + s << "invalidate(" << pyArgName << ");"; + } + s << '\n'; + } + + } else if (!refcount_mods.isEmpty()) { + for (const ArgumentModification &arg_mod : std::as_const(refcount_mods)) { + ReferenceCount refCount = arg_mod.referenceCounts().constFirst(); + if (refCount.action != ReferenceCount::Set + && refCount.action != ReferenceCount::Remove + && refCount.action != ReferenceCount::Add) { + qCWarning(lcShiboken) << "\"set\", \"add\" and \"remove\" are the only values supported by Shiboken for action attribute of reference-count tag."; + continue; + } + const int argIndex = arg_mod.index(); + const QString pyArgName = refCount.action == ReferenceCount::Remove + ? u"Py_None"_s : argumentNameFromIndex(api(), func, argIndex); + + if (refCount.action == ReferenceCount::Add || refCount.action == ReferenceCount::Set) + s << "Shiboken::Object::keepReference("; + else + s << "Shiboken::Object::removeReference("; + + s << "reinterpret_cast<SbkObject *>(self), \""; + QString varName = arg_mod.referenceCounts().constFirst().varName; + if (varName.isEmpty()) + varName = func->minimalSignature() + QString::number(argIndex); + + s << varName << "\", " << pyArgName + << (refCount.action == ReferenceCount::Add ? ", true" : "") + << ");\n"; + + if (argIndex == 0) + hasReturnPolicy = true; + } + } + writeParentChildManagement(s, func, usesPyArgs, !hasReturnPolicy); + + if (generateExceptionHandling) + s << propagateException; +} + +QStringList CppGenerator::getAncestorMultipleInheritance(const AbstractMetaClassCPtr &metaClass) +{ + QStringList result; + const auto &baseClases = metaClass->typeSystemBaseClasses(); + if (!baseClases.isEmpty()) { + for (const auto &baseClass : baseClases) { + QString offset; + QTextStream(&offset) << "reinterpret_cast<uintptr_t>(static_cast<const " + << baseClass->qualifiedCppName() << " *>(class_ptr)) - base"; + result.append(offset); + offset.clear(); + QTextStream(&offset) << "reinterpret_cast<uintptr_t>(static_cast<const " + << baseClass->qualifiedCppName() << " *>(static_cast<const " + << metaClass->qualifiedCppName() + << " *>(static_cast<const void *>(class_ptr)))) - base"; + result.append(offset); + } + + for (const auto &baseClass : baseClases) + result.append(getAncestorMultipleInheritance(baseClass)); + } + return result; +} + +void CppGenerator::writeMultipleInheritanceInitializerFunction(TextStream &s, + const AbstractMetaClassCPtr &metaClass) +{ + QString className = metaClass->qualifiedCppName(); + const QStringList ancestors = getAncestorMultipleInheritance(metaClass); + s << "int *\n" + << multipleInheritanceInitializerFunctionName(metaClass) << "(const void *cptr)\n" + << "{\n" << indent; + s << "static int mi_offsets[] = {-2"; + for (qsizetype i = 0; i < ancestors.size(); i++) + s << ", 0"; + s << "};\n" + << "if (mi_offsets[0] == -2) {\n" << indent + << "const auto *class_ptr = reinterpret_cast<const " << className << " *>(cptr);\n" + << "const auto base = reinterpret_cast<uintptr_t>(class_ptr);\n" + << "int *p = mi_offsets;\n"; + + for (const QString &ancestor : ancestors) + s << "*p++ = int(" << ancestor << ");\n"; + s << "std::sort(mi_offsets, p);\n" + << "auto *end = std::unique(mi_offsets, p);\n" + << "*end++ = -1;\n" + << "if (mi_offsets[0] == 0)\n" + << indent + << "std::memmove(&mi_offsets[0], &mi_offsets[1], (end - mi_offsets - 1) * sizeof(int));\n" + << outdent << outdent + << "}\nreturn mi_offsets;\n" << outdent << "}\n"; +} + +void CppGenerator::writeSpecialCastFunction(TextStream &s, const AbstractMetaClassCPtr &metaClass) +{ + QString className = metaClass->qualifiedCppName(); + s << "static void * " << cpythonSpecialCastFunctionName(metaClass) + << "(void *obj, PyTypeObject *desiredType)\n{\n" << indent + << "auto me = reinterpret_cast< " << m_gsp << className << " *>(obj);\n"; + bool firstClass = true; + const auto &allAncestors = metaClass->allTypeSystemAncestors(); + for (const auto &baseClass : allAncestors) { + if (!firstClass) + s << "else "; + s << "if (desiredType == " << cpythonTypeNameExt(baseClass->typeEntry()) + << ")\n" << indent + << "return static_cast< " << getFullTypeName(baseClass) << " *>(me);\n" + << outdent; + firstClass = false; + } + s << "return me;\n" << outdent << "}\n\n"; +} + +void CppGenerator::writePrimitiveConverterInitialization(TextStream &s, + const CustomConversionPtr &customConversion) +{ + TypeEntryCPtr type = customConversion->ownerType(); + QString converter = converterObject(type); + s << "// Register converter for type '" << type->qualifiedTargetLangName() << "'.\n" + << converter << " = Shiboken::Conversions::createConverter("; + if (!type->hasTargetLangApiType()) + s << "nullptr"; + else if (type->targetLangApiName() == cPyObjectT) + s << "&PyBaseObject_Type"; + else + s << '&' << type->targetLangApiName() << "_Type"; + QString typeName = fixedCppTypeName(type); + s << ", " << cppToPythonFunctionName(typeName, typeName) << ");\n" + << registerConverterName(type->qualifiedCppName(), converter); + writeCustomConverterRegister(s, customConversion, converter); +} + +static void registerConverterInScopes(TextStream &s, QStringView signature, + QAnyStringView varName = converterVar) +{ + while (true) { + s << registerConverterName(signature, varName); + const auto qualifierPos = signature.indexOf("::"_L1); + if (qualifierPos == -1) + break; + signature = signature.sliced(qualifierPos + 2); + } +} + +void CppGenerator::writeEnumConverterInitialization(TextStream &s, const AbstractMetaEnum &metaEnum) +{ + if (metaEnum.isPrivate() || metaEnum.isAnonymous()) + return; + EnumTypeEntryCPtr enumType = metaEnum.typeEntry(); + Q_ASSERT(enumType); + + static const char enumPythonVar[] = "EType"; + + s << "// Register converter for enum '" << enumType->qualifiedCppName() + << "'.\n{\n" << indent; + + const QString typeName = fixedCppTypeName(enumType); + s << "SbkConverter *converter = Shiboken::Conversions::createConverter(" + << enumPythonVar << ',' << '\n' << indent + << cppToPythonFunctionName(typeName, typeName) << ");\n" << outdent; + + const QString toCpp = pythonToCppFunctionName(typeName, typeName); + const QString isConv = convertibleToCppFunctionName(typeName, typeName); + writeAddPythonToCppConversion(s, u"converter"_s, toCpp, isConv); + s << "Shiboken::Enum::setTypeConverter(" << enumPythonVar + << ", converter);\n"; + + registerConverterInScopes(s, enumType->qualifiedCppName()); + if (auto flags = enumType->flags()) + s << "// Register converter for flag '" << flags->qualifiedCppName() << "'.\n" + << registerConverterName(flags->name()) // QMetaType + << registerConverterName(flags->originalName()); // Signals with flags + + s << outdent << "}\n"; +} + +QString CppGenerator::writeContainerConverterInitialization(TextStream &s, + const AbstractMetaType &type, + const ApiExtractorResult &api) +{ + const auto cppSignature = + QString::fromUtf8(QMetaObject::normalizedSignature(type.cppSignature().toUtf8())); + s << "// Register converter for type '" << cppSignature << "'.\n"; + const QString converter = converterObject(type); + s << converter << " = Shiboken::Conversions::createConverter("; + + Q_ASSERT(type.typeEntry()->isContainer()); + const auto typeEntry = std::static_pointer_cast<const ContainerTypeEntry>(type.typeEntry()); + + const QString targetTypeName = containerNativeToTargetTypeName(typeEntry); + if (targetTypeName == cPyObjectT) { + s << "&PyBaseObject_Type"; + } else { + s << '&' << targetTypeName << "_Type"; + } + + const QString typeName = fixedCppTypeName(type); + s << ", " << cppToPythonFunctionName(typeName, targetTypeName) << ");\n"; + + s << registerConverterName(cppSignature, converter); + if (usePySideExtensions() && cppSignature.startsWith("const "_L1) + && cppSignature.endsWith(u'&')) { + auto underlyingType = QStringView{cppSignature}.sliced(6, cppSignature.size() - 7); + s << registerConverterName(underlyingType, converter); + } + + for (const auto &conv : typeEntry->customConversion()->targetToNativeConversions()) { + const QString &sourceTypeName = conv.sourceTypeName(); + QString toCpp = pythonToCppFunctionName(sourceTypeName, typeName); + QString isConv = convertibleToCppFunctionName(sourceTypeName, typeName); + writeAddPythonToCppConversion(s, converter, toCpp, isConv); + } + + auto typedefItPair = api.typedefTargetToName().equal_range(type.cppSignature()); + if (typedefItPair.first != typedefItPair.second) { + auto *typeDb = TypeDatabase::instance(); + s << "// Register converters for type aliases of " << cppSignature << "'.\n"; + for (auto it = typedefItPair.first; it != typedefItPair.second; ++it) { + if (!typeDb->findType(it.value())) + s << registerConverterName(it.value(), converter); + } + } + + return converter; +} + +QString CppGenerator::typeInitStruct(const TypeEntryCPtr &te) +{ + return cppApiVariableName(te->targetLangPackage()) + u'[' + + getTypeIndexVariableName(te) + u']'; +} + +void CppGenerator::writeExtendedConverterInitialization(TextStream &s, + const TypeEntryCPtr &externalType, + const AbstractMetaClassCList &conversions) +{ + s << "// Extended implicit conversions for " << externalType->qualifiedTargetLangName() + << ".\n"; + for (const auto &sourceClass : conversions) { + QString sourceTypeName = fixedCppTypeName(sourceClass->typeEntry()); + QString targetTypeName = fixedCppTypeName(externalType); + QString toCpp = pythonToCppFunctionName(sourceTypeName, targetTypeName); + QString isConv = convertibleToCppFunctionName(sourceTypeName, targetTypeName); + if (!externalType->isPrimitive()) + s << cpythonTypeNameExt(externalType) << ";\n"; + writeAddPythonToCppConversion(s, typeInitStruct(externalType), toCpp, isConv); + } +} + +QString CppGenerator::multipleInheritanceInitializerFunctionName(const AbstractMetaClassCPtr &metaClass) +{ + return cpythonBaseName(metaClass->typeEntry()) + u"_mi_init"_s; +} + +bool CppGenerator::supportsMappingProtocol(const AbstractMetaClassCPtr &metaClass) +{ + for (const auto &m : mappingProtocols()) { + if (metaClass->hasFunction(m.name)) + return true; + } + + return false; +} + +bool CppGenerator::supportsNumberProtocol(const AbstractMetaClassCPtr &metaClass) +{ + return metaClass->hasArithmeticOperatorOverload() + || metaClass->hasIncDecrementOperatorOverload() + || metaClass->hasLogicalOperatorOverload() + || metaClass->hasBitwiseOperatorOverload() + || hasBoolCast(metaClass); +} + +bool CppGenerator::supportsSequenceProtocol(const AbstractMetaClassCPtr &metaClass) +{ + for (const auto &seq : sequenceProtocols()) { + if (metaClass->hasFunction(seq.name)) + return true; + } + + ComplexTypeEntryCPtr baseType = metaClass->typeEntry()->baseContainerType(); + return baseType && baseType->isContainer(); +} + +bool CppGenerator::shouldGenerateGetSetList(const AbstractMetaClassCPtr &metaClass) +{ + for (const AbstractMetaField &f : metaClass->fields()) { + 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; +} + +struct pyTypeSlotEntry +{ + explicit pyTypeSlotEntry(QAnyStringView name, QAnyStringView function) : + m_name(name), m_function(function) {} + + QAnyStringView m_name; + QAnyStringView m_function; +}; + +TextStream &operator<<(TextStream &str, const pyTypeSlotEntry &e) +{ + if (!e.m_function.isEmpty()) { + str << '{' << e.m_name << ',' << Pad(' ', qMax(0, 18 - e.m_name.size())) + << "reinterpret_cast<void *>(" << e.m_function << ")},\n"; + } + return str; +} + +void CppGenerator::writeClassDefinition(TextStream &s, + const AbstractMetaClassCPtr &metaClass, + const GeneratorContext &classContext) +{ + QString tp_init; + QString tp_new; + QString tp_dealloc; + QString tp_hash; + QString tp_call; + const QString className = chopType(cpythonTypeName(metaClass)); + AbstractMetaFunctionCList ctors; + const auto &allCtors = metaClass->queryFunctions(FunctionQueryOption::AnyConstructor); + for (const auto &f : allCtors) { + if (!f->isPrivate() && !f->isModifiedRemoved() + && f->functionType() != AbstractMetaFunction::MoveConstructorFunction) { + ctors.append(f); + } + } + + bool onlyPrivCtor = !metaClass->hasNonPrivateConstructor(); + + const bool isQApp = usePySideExtensions() + && inheritsFrom(metaClass, u"QCoreApplication"_s); + + QString tp_flags = u"Py_TPFLAGS_DEFAULT"_s; + if (!metaClass->attributes().testFlag(AbstractMetaClass::FinalCppClass)) + tp_flags += u"|Py_TPFLAGS_BASETYPE"_s; + if (metaClass->isNamespace() || metaClass->hasPrivateDestructor()) { + tp_dealloc = metaClass->hasPrivateDestructor() ? + u"SbkDeallocWrapperWithPrivateDtor"_s : + u"Sbk_object_dealloc /* PYSIDE-832: Prevent replacement of \"0\" with subtype_dealloc. */"_s; + tp_init.clear(); + } else { + tp_dealloc = isQApp + ? u"&SbkDeallocQAppWrapper"_s : u"&SbkDeallocWrapper"_s; + if (!onlyPrivCtor && !ctors.isEmpty()) + tp_init = cpythonFunctionName(ctors.constFirst()); + } + + const AttroCheck attroCheck = checkAttroFunctionNeeds(metaClass); + const QString tp_getattro = (attroCheck & AttroCheckFlag::GetattroMask) != 0 + ? cpythonGetattroFunctionName(metaClass) : QString(); + const QString tp_setattro = (attroCheck & AttroCheckFlag::SetattroMask) != 0 + ? cpythonSetattroFunctionName(metaClass) : QString(); + + if (metaClass->hasPrivateDestructor() || onlyPrivCtor) { + // tp_flags = u"Py_TPFLAGS_DEFAULT"_s; + // This is not generally possible, because PySide does not care about + // privacy the same way. This worked before the heap types were used, + // because inheritance is not really checked for static types. + // Instead, we check this at runtime, see SbkObjectType_tp_new. + if (metaClass->fullName().startsWith(u"PySide6.Qt")) { + // PYSIDE-595: No idea how to do non-inheritance correctly. + // Since that is only relevant in shiboken, I used a shortcut for + // PySide. + tp_new = u"SbkObject_tp_new"_s; + } + else { + tp_new = u"SbkDummyNew /* PYSIDE-595: Prevent replacement " + "of \"0\" with base->tp_new. */"_s; + } + } + else if (isQApp) { + tp_new = u"SbkQApp_tp_new"_s; // PYSIDE-571: need singleton app + } + else { + tp_new = u"SbkObject_tp_new"_s; + } + tp_flags.append(u"|Py_TPFLAGS_HAVE_GC"_s); + + QString tp_richcompare; + if (generateRichComparison(classContext)) + tp_richcompare = cpythonBaseName(metaClass) + u"_richcompare"_s; + + const bool isSmartPointer = classContext.forSmartPointer(); + QString tp_getset; + if (shouldGenerateGetSetList(metaClass) && !isSmartPointer) + tp_getset = cpythonGettersSettersDefinitionName(metaClass); + + // search for special functions + clearTpFuncs(); + for (const auto &func : metaClass->functions()) { + // Special non-operator functions identified by name + auto it = m_tpFuncs.find(func->name()); + if (it != m_tpFuncs.end()) + it.value() = cpythonFunctionName(func); + else if ( it = m_nbFuncs.find(func->name()); it != m_nbFuncs.end() ) + it.value() = cpythonFunctionName(func); + } + if (m_tpFuncs.value(REPR_FUNCTION).isEmpty() + && (isSmartPointer || metaClass->hasToStringCapability())) { + const QString name = isSmartPointer + ? writeSmartPointerReprFunction(s, classContext) + : writeReprFunction(s, classContext, metaClass->toStringCapabilityIndirections()); + m_tpFuncs[REPR_FUNCTION] = name; + } + + // class or some ancestor has multiple inheritance + const auto miClass = getMultipleInheritingClass(metaClass); + if (miClass) { + if (metaClass == miClass) + writeMultipleInheritanceInitializerFunction(s, metaClass); + writeSpecialCastFunction(s, metaClass); + s << '\n'; + } + + s << "// Class Definition -----------------------------------------------\n" + "extern \"C\" {\n"; + + if (hasHashFunction(metaClass)) + tp_hash = u'&' + cpythonBaseName(metaClass) + u"_HashFunc"_s; + + const auto callOp = metaClass->findFunction("operator()"); + if (callOp && !callOp->isModifiedRemoved()) + tp_call = u'&' + cpythonFunctionName(callOp); + + const QString typePtr = u"_"_s + className + + u"_Type"_s; + s << "static PyTypeObject *" << typePtr << " = nullptr;\n" + << "static PyTypeObject *" << className << "_TypeF(void)\n" + << "{\n" << indent << "return " << typePtr << ";\n" << outdent + << "}\n\nstatic PyType_Slot " << className << "_slots[] = {\n" << indent + << "{Py_tp_base, nullptr}, // inserted by introduceWrapperType\n" + << pyTypeSlotEntry("Py_tp_dealloc", tp_dealloc) + << pyTypeSlotEntry("Py_tp_repr", m_tpFuncs.value(REPR_FUNCTION)) + << pyTypeSlotEntry("Py_tp_hash", tp_hash) + << pyTypeSlotEntry("Py_tp_call", tp_call) + << pyTypeSlotEntry("Py_tp_str", m_tpFuncs.value(u"__str__"_s)) + << pyTypeSlotEntry("Py_tp_getattro", tp_getattro) + << pyTypeSlotEntry("Py_tp_setattro", tp_setattro) + << pyTypeSlotEntry("Py_tp_traverse", className + u"_traverse"_s) + << pyTypeSlotEntry("Py_tp_clear", className + u"_clear"_s) + << pyTypeSlotEntry("Py_tp_richcompare", tp_richcompare) + << pyTypeSlotEntry("Py_tp_iter", m_tpFuncs.value(u"__iter__"_s)) + << pyTypeSlotEntry("Py_tp_iternext", m_tpFuncs.value(u"__next__"_s)) + << pyTypeSlotEntry("Py_tp_methods", className + u"_methods"_s) + << pyTypeSlotEntry("Py_tp_getset", tp_getset) + << pyTypeSlotEntry("Py_tp_init", tp_init) + << pyTypeSlotEntry("Py_tp_new", tp_new); + if (supportsSequenceProtocol(metaClass)) { + s << "// type supports sequence protocol\n"; + writeTypeAsSequenceDefinition(s, metaClass); + } + if (supportsMappingProtocol(metaClass)) { + s << "// type supports mapping protocol\n"; + writeTypeAsMappingDefinition(s, metaClass); + } + if (supportsNumberProtocol(metaClass)) { + s << "// type supports number protocol\n"; + writeTypeAsNumberDefinition(s, metaClass); + } + s << "{0, " << NULL_PTR << "}\n" << outdent << "};\n"; + + int packageLevel = packageName().count(u'.') + 1; + s << "static PyType_Spec " << className << "_spec = {\n" << indent + << '"' << packageLevel << ':' << getClassTargetFullName(metaClass) << "\",\n" + << "sizeof(SbkObject),\n0,\n" << tp_flags << ",\n" + << className << "_slots\n" << outdent + << "};\n\n} //extern \"C\"\n"; +} + +void CppGenerator::writeMappingMethods(TextStream &s, + const AbstractMetaClassCPtr &metaClass, + const GeneratorContext &context) const +{ + for (const auto & m : mappingProtocols()) { + const auto func = metaClass->findFunction(m.name); + if (!func) + continue; + QString funcName = cpythonFunctionName(func); + CodeSnipList snips = func->injectedCodeSnips(TypeSystem::CodeSnipPositionAny, TypeSystem::TargetLangCode); + s << m.returnType << ' ' << funcName << '(' << m.arguments << ")\n{\n" << indent; + + writeCppSelfDefinition(s, func, context, ErrorReturn::Default); + + const AbstractMetaArgument *lastArg = func->arguments().isEmpty() + ? nullptr : &func->arguments().constLast(); + writeCodeSnips(s, snips, TypeSystem::CodeSnipPositionAny, + TypeSystem::TargetLangCode, func, false, lastArg); + s << outdent << "}\n\n"; + } +} + +void CppGenerator::writeSequenceMethods(TextStream &s, + const AbstractMetaClassCPtr &metaClass, + const GeneratorContext &context) const +{ + bool injectedCode = false; + + for (const auto &seq : sequenceProtocols()) { + const auto func = metaClass->findFunction(seq.name); + if (!func) + continue; + injectedCode = true; + QString funcName = cpythonFunctionName(func); + + CodeSnipList snips = func->injectedCodeSnips(TypeSystem::CodeSnipPositionAny, TypeSystem::TargetLangCode); + s << seq.returnType << ' ' << funcName << '(' << seq.arguments << ")\n{\n" << indent; + + writeCppSelfDefinition(s, func, context, ErrorReturn::Default); + + const AbstractMetaArgument *lastArg = func->arguments().isEmpty() ? nullptr : &func->arguments().constLast(); + writeCodeSnips(s, snips,TypeSystem::CodeSnipPositionAny, + TypeSystem::TargetLangCode, func, false /* uses PyArgs */, lastArg); + s<< outdent << "}\n\n"; + } + + if (!injectedCode) + writeDefaultSequenceMethods(s, context); +} + +// Sequence protocol structure member names +static const QHash<QString, QString> &sqFuncs() +{ + static const QHash<QString, QString> result = { + {u"__concat__"_s, u"Py_sq_concat"_s}, + {u"__contains__"_s, u"Py_sq_contains"_s}, + {u"__getitem__"_s, u"Py_sq_item"_s}, + {u"__getslice__"_s, u"Py_sq_slice"_s}, + {u"__len__"_s, u"Py_sq_length"_s}, + {u"__setitem__"_s, u"Py_sq_ass_item"_s}, + {u"__setslice__"_s, u"Py_sq_ass_slice"_s} + }; + return result; +} + +void CppGenerator::writeTypeAsSequenceDefinition(TextStream &s, + const AbstractMetaClassCPtr &metaClass) +{ + bool hasFunctions = false; + QMap<QString, QString> funcs; + for (const auto &seq : sequenceProtocols()) { + const auto func = metaClass->findFunction(seq.name); + if (func) { + funcs.insert(seq.name, u'&' + cpythonFunctionName(func)); + hasFunctions = true; + } + } + + QString baseName = cpythonBaseName(metaClass); + + //use default implementation + if (!hasFunctions) { + funcs[u"__len__"_s] = baseName + u"__len__"_s; + funcs[u"__getitem__"_s] = baseName + u"__getitem__"_s; + funcs[u"__setitem__"_s] = baseName + u"__setitem__"_s; + } + + for (auto it = sqFuncs().cbegin(), end = sqFuncs().cend(); it != end; ++it) { + const QString &sqName = it.key(); + auto fit = funcs.constFind(sqName); + if (fit != funcs.constEnd()) + s << pyTypeSlotEntry(it.value(), fit.value()); + } +} + +void CppGenerator::writeTypeAsMappingDefinition(TextStream &s, + const AbstractMetaClassCPtr &metaClass) +{ + // Sequence protocol structure members names + static const QHash<QString, QString> mpFuncs{ + {u"__mlen__"_s, u"Py_mp_length"_s}, + {u"__mgetitem__"_s, u"Py_mp_subscript"_s}, + {u"__msetitem__"_s, u"Py_mp_ass_subscript"_s}, + }; + QMap<QString, QString> funcs; + for (const auto &m : mappingProtocols()) { + const auto func = metaClass->findFunction(m.name); + if (func) { + const QString entry = u"reinterpret_cast<void *>(&"_s + + cpythonFunctionName(func) + u')'; + funcs.insert(m.name, entry); + } + } + + for (auto it = mpFuncs.cbegin(), end = mpFuncs.cend(); it != end; ++it) { + const auto fit = funcs.constFind(it.key()); + if (fit != funcs.constEnd()) + s << pyTypeSlotEntry(it.value(), fit.value()); + } +} + +// Number protocol structure members names +static const QHash<QString, QString> &nbFuncs() +{ + static const QHash<QString, QString> result = { + {u"__abs__"_s, u"Py_nb_absolute"_s}, + {u"__add__"_s, u"Py_nb_add"_s}, + {u"__sub__"_s, u"Py_nb_subtract"_s}, + {u"__mul__"_s, u"Py_nb_multiply"_s}, + {u"__div__"_s, u"Py_nb_true_divide"_s}, + {u"__mod__"_s, u"Py_nb_remainder"_s}, + {u"__neg__"_s, u"Py_nb_negative"_s}, + {u"__pos__"_s, u"Py_nb_positive"_s}, + {u"__pow__"_s, u"Py_nb_power"_s}, + {u"__invert__"_s, u"Py_nb_invert"_s}, + {u"__lshift__"_s, u"Py_nb_lshift"_s}, + {u"__rshift__"_s, u"Py_nb_rshift"_s}, + {u"__and__"_s, u"Py_nb_and"_s}, + {u"__xor__"_s, u"Py_nb_xor"_s}, + {u"__or__"_s, u"Py_nb_or"_s}, + {u"__iadd__"_s, u"Py_nb_inplace_add"_s}, + {u"__isub__"_s, u"Py_nb_inplace_subtract"_s}, + {u"__imul__"_s, u"Py_nb_inplace_multiply"_s}, + {u"__imod__"_s, u"Py_nb_inplace_remainder"_s}, + {u"__ilshift__"_s, u"Py_nb_inplace_lshift"_s}, + {u"__irshift__"_s, u"Py_nb_inplace_rshift"_s}, + {u"__iand__"_s, u"Py_nb_inplace_and"_s}, + {u"__ixor__"_s, u"Py_nb_inplace_xor"_s}, + {u"__ior__"_s, u"Py_nb_inplace_or"_s}, + {u"__bool__"_s, u"Py_nb_bool"_s}, + {u"__int__"_s, u"Py_nb_int"_s}, + {u"__float__"_s, u"Py_nb_float"_s} + }; + return result; +} + +void CppGenerator::writeTypeAsNumberDefinition(TextStream &s, const AbstractMetaClassCPtr &metaClass) const +{ + QMap<QString, QString> nb; + + const QList<AbstractMetaFunctionCList> opOverloads = numberProtocolOperators(metaClass); + for (const auto &opOverload : opOverloads) { + const auto rfunc = opOverload.at(0); + QString opName = ShibokenGenerator::pythonOperatorFunctionName(rfunc); + nb[opName] = cpythonFunctionName(rfunc); + } + + for (auto it = m_nbFuncs.cbegin(), end = m_nbFuncs.cend(); it != end; ++it) { + if (!it.value().isEmpty()) + nb.insert(it.key(), it.value()); + } + + QString baseName = cpythonBaseName(metaClass); + + if (hasBoolCast(metaClass)) + nb.insert(u"__bool__"_s, baseName + u"___nb_bool"_s); + + for (auto it = nbFuncs().cbegin(), end = nbFuncs().cend(); it != end; ++it) { + const QString &nbName = it.key(); + const auto nbIt = nb.constFind(nbName); + if (nbIt != nb.constEnd()) + s << pyTypeSlotEntry(it.value(), nbIt.value()); + } +} + +void CppGenerator::writeTpTraverseFunction(TextStream &s, const AbstractMetaClassCPtr &metaClass) +{ + QString baseName = cpythonBaseName(metaClass); + s << "static int " << baseName + << "_traverse(PyObject *self, visitproc visit, void *arg)\n{\n" << indent + << "auto traverseProc = " + << pyTypeGetSlot("traverseproc", sbkObjectTypeF, "Py_tp_traverse") << ";\n" + << "return traverseProc(self, visit, arg);\n" + << outdent << "}\n"; +} + +void CppGenerator::writeTpClearFunction(TextStream &s, const AbstractMetaClassCPtr &metaClass) +{ + QString baseName = cpythonBaseName(metaClass); + s << "static int " << baseName << "_clear(PyObject *self)\n{\n" << indent + << "auto clearProc = " + << pyTypeGetSlot("inquiry", sbkObjectTypeF, "Py_tp_clear") << ";\n" + << "return clearProc(self);\n" + << outdent << "}\n"; +} + +QString CppGenerator::writeCopyFunction(TextStream &s, + TextStream &definitionStream, + TextStream &signatureStream, + const GeneratorContext &context) +{ + const auto metaClass = context.metaClass(); + const QString className = chopType(cpythonTypeName(metaClass)); + const QString funcName = className + u"__copy__"_s; + + signatureStream << fullPythonClassName(metaClass) << ".__copy__()\n"; + definitionStream << PyMethodDefEntry{u"__copy__"_s, funcName, {"METH_NOARGS"_ba}, {}} + << ",\n"; + + s << "static PyObject *" << funcName << "(PyObject *self)\n" + << "{\n" << indent; + writeCppSelfDefinition(s, context, ErrorReturn::Default, CppSelfDefinitionFlag::CppSelfAsReference); + QString conversionCode; + if (!context.forSmartPointer()) + conversionCode = cpythonToPythonConversionFunction(metaClass); + else + conversionCode = cpythonToPythonConversionFunction(context.preciseType()); + + s << "PyObject *" << PYTHON_RETURN_VAR << " = " << conversionCode + << CPP_SELF_VAR << ");\n"; + writeFunctionReturnErrorCheckSection(s, ErrorReturn::Default); + s << "return " << PYTHON_RETURN_VAR << ";\n" << outdent + << "}\n\n"; + + return funcName; +} + +static inline void writeGetterFunctionStart(TextStream &s, const QString &funcName) +{ + s << "static PyObject *" << funcName << "(PyObject *self, void * /* closure */)\n" + << "{\n" << indent; +} + +QString CppGenerator::cppFieldAccess(const AbstractMetaField &metaField, + const GeneratorContext &context) +{ + QString result; + QTextStream str(&result); + if (avoidProtectedHack() && metaField.isProtected()) + str << "static_cast<" << context.wrapperName() << " *>(" << CPP_SELF_VAR << ')'; + else + str << CPP_SELF_VAR; + str << "->" << metaField.originalName(); + return result; +} + +void CppGenerator::writeGetterFunction(TextStream &s, + const AbstractMetaField &metaField, + const GeneratorContext &context) +{ + writeGetterFunctionStart(s, cpythonGetterFunctionName(metaField)); + + writeCppSelfDefinition(s, context); + + const AbstractMetaType &fieldType = metaField.type(); + // Force use of pointer to return internal variable memory + bool newWrapperSameObject = !fieldType.isConstant() && fieldType.isWrapperType() + && !fieldType.isPointer(); + + QString cppField = cppFieldAccess(metaField, context); + + if (metaField.generateOpaqueContainer() + && fieldType.generateOpaqueContainer()) { + const QString creationFunc = opaqueContainerCreationFunc(fieldType); + writeOpaqueContainerCreationFuncDecl(s, creationFunc, fieldType); + s << "PyObject *pyOut = " << creationFunc + << "(&" << cppField << ");\nPy_IncRef(pyOut);\n" + << "return pyOut;\n" << outdent << "}\n"; + return; + } + + if (newWrapperSameObject) { + cppField.prepend(u"&("); + cppField.append(u')'); + } + + if (fieldType.isCppIntegralPrimitive() || fieldType.isEnum()) { + s << getFullTypeNameWithoutModifiers(fieldType) << " cppOut_local = " << cppField << ";\n"; + cppField = u"cppOut_local"_s; + } + + s << "PyObject *pyOut = {};\n"; + if (newWrapperSameObject) { + // Special case colocated field with same address (first field in a struct) + s << "if (reinterpret_cast<void *>(" + << cppField << ") == reinterpret_cast<void *>(" + << CPP_SELF_VAR << ")) {\n" << indent + << "pyOut = reinterpret_cast<PyObject *>(Shiboken::Object::findColocatedChild(" + << "reinterpret_cast<SbkObject *>(self), " + << cpythonTypeNameExt(fieldType) << "));\n" + << "if (pyOut != nullptr) {\n" << indent + << "Py_IncRef(pyOut);\n" + << "return pyOut;\n" + << outdent << "}\n"; + // Check if field wrapper has already been created. + s << outdent << "} else if (Shiboken::BindingManager::instance().hasWrapper(" + << cppField << ")) {" << "\n" << indent + << "pyOut = reinterpret_cast<PyObject *>(Shiboken::BindingManager::instance().retrieveWrapper(" + << cppField << "));" << "\n" + << "Py_IncRef(pyOut);" << "\n" + << "return pyOut;" << "\n" + << outdent << "}\n"; + // Create and register new wrapper. We force a pointer conversion also + // for wrapped value types so that they refer to the struct member, + // avoiding any trouble copying them. Add a parent relationship to + // properly notify if the struct is deleted (see protected_test.py, + // testProtectedValueTypeProperty()). Note that this has currently + // unsolved issues when using temporary Python lists of structs + // which can cause elements to be reported deleted in expressions like + // "foo.list_of_structs[2].field". + s << "pyOut = " + << "Shiboken::Object::newObject(" << cpythonTypeNameExt(fieldType) + << ", " << cppField << ", false, true);\n" + << "Shiboken::Object::setParent(self, pyOut)"; + } else { + s << "pyOut = "; + writeToPythonConversion(s, fieldType, metaField.enclosingClass(), cppField); + } + s << ";\nreturn pyOut;\n" << outdent << "}\n"; +} + +// Write a getter for QPropertySpec +void CppGenerator::writeGetterFunction(TextStream &s, + const QPropertySpec &property, + const GeneratorContext &context) +{ + writeGetterFunctionStart(s, cpythonGetterFunctionName(property, context.metaClass())); + writeCppSelfDefinition(s, context); + const QString value = "value"_L1; + s << "auto " << value << " = " << CPP_SELF_VAR << "->" << property.read() << "();\n" + << "auto *pyResult = "; + writeToPythonConversion(s, property.type(), context.metaClass(), value); + s << ";\nif (" << shibokenErrorsOccurred << " || pyResult == nullptr) {\n" + << indent << "Py_XDECREF(pyResult);\nreturn {};\n" << outdent + << "}\nreturn pyResult;\n" << outdent << "}\n\n"; +} + +// Write setter function preamble (type checks on "pyIn") +void CppGenerator::writeSetterFunctionPreamble(TextStream &s, + const QString &name, + const QString &funcName, + const AbstractMetaType &type, + const GeneratorContext &context) +{ + s << "static int " << funcName << "(PyObject *self, PyObject *pyIn, void * /* closure */)\n" + << "{\n" << indent; + + writeCppSelfDefinition(s, context, ErrorReturn::Zero); + + s << "if (pyIn == " << NULL_PTR << ") {\n" << indent + << "Shiboken::Errors::setInvalidTypeDeletion(\"" << name << "\");\n" + << "return -1;\n" + << outdent << "}\n"; + + s << PYTHON_TO_CPPCONVERSION_STRUCT << ' ' << PYTHON_TO_CPP_VAR << ";\n" + << "if (!"; + writeTypeCheck(s, type, u"pyIn"_s, isNumber(type.typeEntry())); + s << ") {\n" << indent + << "Shiboken::Errors::setSetterTypeError(\"" << name << "\", \"" + << type.name() << "\");\n" + << "return -1;\n" + << outdent << "}\n\n"; +} + +void CppGenerator::writeSetterFunction(TextStream &s, + const AbstractMetaField &metaField, + const GeneratorContext &context) +{ + const AbstractMetaType &fieldType = metaField.type(); + writeSetterFunctionPreamble(s, metaField.name(), cpythonSetterFunctionName(metaField), + fieldType, context); + + + const QString cppField = cppFieldAccess(metaField, context); + + if (fieldType.isCppIntegralPrimitive() || fieldType.typeEntry()->isEnum() + || fieldType.typeEntry()->isFlags()) { + s << "auto cppOut_local = " << cppField << ";\n" + << PYTHON_TO_CPP_VAR << "(pyIn, &cppOut_local);\n" + << cppField << " = cppOut_local"; + } else { + if (fieldType.isPointerToConst()) + s << "const "; + s << "auto " << QByteArray(fieldType.indirections(), '*') + << "&cppOut_ptr = " << cppField << ";\n" + << PYTHON_TO_CPP_VAR << "(pyIn, &cppOut_ptr)"; + } + s << ";\n\n"; + + if (fieldType.isPointerToWrapperType()) { + s << "Shiboken::Object::keepReference(reinterpret_cast<SbkObject *>(self), \"" + << metaField.name() << "\", pyIn);\n"; + } + + s << "return 0;\n" << outdent << "}\n"; +} + +// Write a setter for QPropertySpec +void CppGenerator::writeSetterFunction(TextStream &s, + const QPropertySpec &property, + const GeneratorContext &context) +{ + writeSetterFunctionPreamble(s, + property.name(), + cpythonSetterFunctionName(property, context.metaClass()), + property.type(), context); + + s << "auto cppOut = " << CPP_SELF_VAR << "->" << property.read() << "();\n" + << PYTHON_TO_CPP_VAR << "(pyIn, &cppOut);\n" + << "if (" << shibokenErrorsOccurred << ")\n" << indent + << "return -1;\n" << outdent + << CPP_SELF_VAR << "->" << property.write() << "(cppOut);\n" + << "return 0;\n" << outdent << "}\n\n"; +} + +void CppGenerator::writeRichCompareFunctionHeader(TextStream &s, + const QString &baseName, + const GeneratorContext &context) +{ + s << "static PyObject * "; + s << baseName << "_richcompare(PyObject *self, PyObject *" << PYTHON_ARG + << ", int op)\n{\n" << indent; + writeCppSelfDefinition(s, context, ErrorReturn::Default, CppSelfDefinitionFlag::CppSelfAsReference); + s << sbkUnusedVariableCast(CPP_SELF_VAR) + << "PyObject *" << PYTHON_RETURN_VAR << "{};\n" + << PYTHON_TO_CPPCONVERSION_STRUCT << ' ' << PYTHON_TO_CPP_VAR << ";\n" + << sbkUnusedVariableCast(PYTHON_TO_CPP_VAR) << '\n'; +} + +void CppGenerator::writeRichCompareFunction(TextStream &s, + const GeneratorContext &context) const +{ + const auto metaClass = context.metaClass(); + QString baseName = cpythonBaseName(metaClass); + writeRichCompareFunctionHeader(s, baseName, context); + + s << "switch (op) {\n" << indent; + const QList<AbstractMetaFunctionCList> &groupedFuncs = + filterGroupedOperatorFunctions(metaClass, OperatorQueryOption::ComparisonOp); + for (const AbstractMetaFunctionCList &overloads : groupedFuncs) { + const auto rfunc = overloads[0]; + + const auto op = rfunc->comparisonOperatorType().value(); + s << "case " << AbstractMetaFunction::pythonRichCompareOpCode(op) + << ":\n" << indent; + + int alternativeNumericTypes = 0; + for (const auto &func : overloads) { + if (!func->isStatic() && + ShibokenGenerator::isNumber(func->arguments().at(0).type().typeEntry())) + alternativeNumericTypes++; + } + + bool first = true; + OverloadData overloadData(overloads, api()); + const OverloadDataList &nextOverloads = overloadData.children(); + for (const auto &od : nextOverloads) { + const auto func = od->referenceFunction(); + if (func->isStatic()) + continue; + auto argType = getArgumentType(func, 0); + if (!first) { + s << " else "; + } else { + first = false; + } + s << "if ("; + writeTypeCheck(s, argType, PYTHON_ARG, + alternativeNumericTypes == 1 || isPyInt(argType)); + s << ") {\n" << indent + << "// " << func->signature() << '\n'; + writeArgumentConversion(s, argType, CPP_ARG0, + PYTHON_ARG, ErrorReturn::Default, + metaClass, + QString(), func->isUserAdded()); + // If the function is user added, use the inject code + bool generateOperatorCode = true; + if (func->isUserAdded()) { + CodeSnipList snips = func->injectedCodeSnips(); + if (!snips.isEmpty()) { + writeCodeSnips(s, snips, TypeSystem::CodeSnipPositionAny, + TypeSystem::TargetLangCode, func, + false /* uses PyArgs */, &func->arguments().constLast()); + generateOperatorCode = false; + } + } + if (generateOperatorCode) { + if (!func->isVoid()) + s << func->type().cppSignature() << " " << CPP_RETURN_VAR << " = "; + // expression + if (func->isPointerOperator()) + s << '&'; + s << CPP_SELF_VAR << ' ' + << AbstractMetaFunction::cppComparisonOperator(op) << " ("; + auto generatorArg = GeneratorArgument::fromMetaType(argType); + if (generatorArg.indirections != 0) + s << QByteArray(generatorArg.indirections, '*'); + s << CPP_ARG0 << ");\n" + << PYTHON_RETURN_VAR << " = "; + if (!func->isVoid()) { + writeToPythonConversion(s, func->type(), metaClass, + CPP_RETURN_VAR); + } else { + s << "Py_None;\n" << "Py_INCREF(Py_None)"; + } + s << ";\n"; + } + s << outdent << '}'; + } + + s << " else {\n"; + if (op == AbstractMetaFunction::OperatorEqual || + op == AbstractMetaFunction::OperatorNotEqual) { + s << indent << PYTHON_RETURN_VAR << " = " + << (op == AbstractMetaFunction::OperatorEqual ? "Py_False" : "Py_True") << ";\n" + << "Py_INCREF(" << PYTHON_RETURN_VAR << ");\n" << outdent; + } else { + s << indent << "return Shiboken::returnFromRichCompare(" + << PYTHON_RETURN_VAR << ");\n" << outdent; + } + s << "}\n\n"; + + s << "break;\n" << outdent; + } + s << "default:\n" << indent + << richCompareComment + << "return FallbackRichCompare(self, " << PYTHON_ARG << ", op);\n" + << outdent << outdent << "}\n\n" + << "return Shiboken::returnFromRichCompare(" << PYTHON_RETURN_VAR << ");\n" << outdent + << "}\n\n"; +} + +// Return a flag combination for PyMethodDef +QByteArrayList CppGenerator::methodDefinitionParameters(const OverloadData &overloadData) const +{ + const bool usePyArgs = overloadData.pythonFunctionWrapperUsesListOfArguments(); + int min = overloadData.minArgs(); + int max = overloadData.maxArgs(); + + QByteArrayList result; + if ((min == max) && (max < 2) && !usePyArgs) { + result.append(max == 0 ? QByteArrayLiteral("METH_NOARGS") + : QByteArrayLiteral("METH_O")); + } else { + result.append(QByteArrayLiteral("METH_VARARGS")); + if (overloadData.hasArgumentWithDefaultValue()) + result.append(QByteArrayLiteral("METH_KEYWORDS")); + } + // METH_STATIC causes a crash when used for global functions (also from + // invisible namespaces). + const auto ownerClass = overloadData.referenceFunction()->ownerClass(); + if (ownerClass + && !invisibleTopNamespaces().contains(std::const_pointer_cast<AbstractMetaClass>(ownerClass))) { + if (overloadData.hasStaticFunction()) + result.append(QByteArrayLiteral("METH_STATIC")); + if (overloadData.hasClassMethod()) + result.append(QByteArrayLiteral("METH_CLASS")); + } + return result; +} + +QList<PyMethodDefEntry> + CppGenerator::methodDefinitionEntries(const OverloadData &overloadData) const +{ + + const QStringList names = overloadData.referenceFunction()->definitionNames(); + const QString funcName = cpythonFunctionName(overloadData.referenceFunction()); + const QByteArrayList parameters = methodDefinitionParameters(overloadData); + + QList<PyMethodDefEntry> result; + result.reserve(names.size()); + for (const auto &name : names) + result.append({name, funcName, parameters, {}}); + return result; +} + +// Format the type signature of a function parameter +QString CppGenerator::signatureParameter(const AbstractMetaArgument &arg) const +{ + QString result; + QTextStream s(&result); + + auto metaType = arg.type(); + if (auto viewOn = metaType.viewOn()) + metaType = *viewOn; + s << arg.name() << ':'; + + QStringList signatures(metaType.pythonSignature()); + + // Implicit conversions (C++): Check for converting constructors + // "QColor(Qt::GlobalColor)" or conversion operators + const AbstractMetaFunctionCList conversions = + api().implicitConversions(metaType); + for (const auto &f : conversions) { + if (f->isConstructor() && !f->arguments().isEmpty()) { + // PYSIDE-2712: modified types from converting constructors are not always correct + // candidates if they are modified by the type system reference + if (!f->arguments().constFirst().isTypeModified()) { + signatures << f->arguments().constFirst().type().pythonSignature(); + } + } else if (f->isConversionOperator()) { + signatures << f->ownerClass()->fullName(); + } + } + + const qsizetype size = signatures.size(); + if (size > 1) + s << "typing.Union["; + for (qsizetype i = 0; i < size; ++i) { + if (i > 0) + s << ", "; + s << signatures.at(i); + } + if (size > 1) + s << ']'; + + return result; +} + +void CppGenerator::writeSignatureInfo(TextStream &s, const OverloadData &overloadData) const +{ + const auto rfunc = overloadData.referenceFunction(); + QString funcName = fullPythonFunctionName(rfunc, false); + + int idx = overloadData.overloads().length() - 1; + bool multiple = idx > 0; + + for (const auto &f : overloadData.overloads()) { + QStringList args; + // PYSIDE-1328: `self`-ness cannot be computed in Python because there are mixed cases. + // Toplevel functions like `PySide6.QtCore.QEnum` are always self-less. + if (!(f->isStatic()) && f->ownerClass()) + args << PYTHON_SELF_VAR; + const auto &arguments = f->arguments(); + for (qsizetype i = 0, size = arguments.size(); i < size; ++i) { + const auto n = i + 1; + const auto &arg = arguments.at(i); + if (!f->argumentRemoved(n)) { + QString t = f->pyiTypeReplaced(n); + if (t.isEmpty()) { + t = signatureParameter(arg); + } else { + t.prepend(u':'); + t.prepend(arg.name()); + } + QString defaultValue = arg.defaultValueExpression(); + if (!defaultValue.isEmpty()) + t += u'=' + defaultValue.replace(u"::"_s, u"."_s); + args.append(t); + } + } + + // mark the multiple signatures as such, to make it easier to generate different code + if (multiple) + s << idx-- << ':'; + s << funcName << '(' << args.join(u',') << ')'; + + QString returnType = f->pyiTypeReplaced(0); // pyi or modified type + if (returnType.isEmpty() && !f->isVoid()) + returnType = f->type().pythonSignature(); + if (!returnType.isEmpty()) + s << "->" << returnType; + + s << '\n'; + } +} + +void CppGenerator::writeEnumsInitialization(TextStream &s, AbstractMetaEnumList &enums) +{ + if (enums.isEmpty()) + return; + bool preambleWritten = false; + bool etypeUsed = false; + + for (const AbstractMetaEnum &cppEnum : std::as_const(enums)) { + if (cppEnum.isPrivate()) + continue; + if (!preambleWritten) { + s << "// Initialization of enums.\n" + << "Shiboken::AutoDecRef tpDict{};\n" + << "PyTypeObject *EType{};\n\n"; + preambleWritten = true; + } + ConfigurableScope configScope(s, cppEnum.typeEntry()); + etypeUsed |= writeEnumInitialization(s, cppEnum); + } + if (preambleWritten && !etypeUsed) + s << sbkUnusedVariableCast("EType"); +} + +static qsizetype maxLineLength(const QStringList &list) +{ + qsizetype result = 0; + for (const auto &s : list) { + if (auto len = s.size(); len > result) + result = len; + } + return result; +} + +bool CppGenerator::writeEnumInitialization(TextStream &s, const AbstractMetaEnum &cppEnum) +{ + const auto enclosingClass = cppEnum.targetLangEnclosingClass(); + const bool hasUpperEnclosingClass = enclosingClass + && enclosingClass->targetLangEnclosingClass(); + EnumTypeEntryCPtr enumTypeEntry = cppEnum.typeEntry(); + QString enclosingObjectVariable; + if (enclosingClass) + enclosingObjectVariable = cpythonTypeName(enclosingClass); + else if (hasUpperEnclosingClass) + enclosingObjectVariable = u"enclosingClass"_s; + else + enclosingObjectVariable = u"module"_s; + + s << "// Initialization of "; + s << (cppEnum.isAnonymous() ? "anonymous enum identified by enum value" : "enum"); + s << " '" << cppEnum.name() << "'.\n"; + + const QString userType = cppEnum.typeEntry()->cppType(); + const bool isSigned = cppEnum.isSigned() && !userType.contains(u"unsigned"_s); + const bool isAccessible = !avoidProtectedHack() || !cppEnum.isProtected(); + const auto enumValues = cppEnum.nonRejectedValues(); + + const QString prefix = cppEnum.name(); + + const QString intType = userType.isEmpty() ? cppEnum.underlyingType() : userType; + + // Create a list of values + const QString initializerValues = prefix + u"_InitializerValues"_s; + const QString initializerName = prefix + u"_Initializer"_s; + + // Build maybe array of enum names. + if (cppEnum.enumKind() != AnonymousEnum) { + s << "const char *" << initializerName << "[] = {\n" << indent; + for (const auto &enumValue : enumValues) { + QString name = mangleName(enumValue.name()); + s << '\"' << name << "\",\n"; + } + s << "nullptr};\n" << outdent; + } + + int targetHexLen = 0; + QString usedIntType = userType; + if (usedIntType.isEmpty()) { + const int usedBits = cppEnum.usedBits(); + targetHexLen = usedBits / 4; + usedIntType = AbstractMetaEnum::intTypeForSize(usedBits, cppEnum.isSigned()); + } + + if (usedIntType != intType) + s << "// \"" << usedIntType << "\" used instead of \"" << intType << "\"\n"; + + // Calculating formatting columns + QString enumValuePrefix; + if (isAccessible) { + if (cppEnum.enclosingClass()) + enumValuePrefix += cppEnum.enclosingClass()->qualifiedCppName() + u"::"_s; + if (!cppEnum.isAnonymous()) + enumValuePrefix += cppEnum.name() + u"::"_s; + } + + // Build array of enum values + if (enumValues.isEmpty()) { + s << "const " << usedIntType << " *" << initializerValues << "{};\n"; + } else { + QStringList values; + values.reserve(enumValues.size()); + s << "constexpr " << usedIntType << ' ' << initializerValues << "[] = {\n" << indent; + for (qsizetype idx = 0, last = enumValues.size() - 1; idx <= last; ++idx) { + const auto &enumValue = enumValues.at(idx); + QString line = usedIntType + u'(' + (isAccessible + ? enumValuePrefix + enumValue.name() + : enumValue.value().toString()) + u')'; + if (idx != last) + line += u','; + values.append(line); + } + + const auto len = maxLineLength(values) + 1; + for (qsizetype idx = 0, size = enumValues.size(); idx < size; ++idx) { + const auto &enumValue = enumValues.at(idx).value(); + const char *numberSpace = enumValue.isNegative() ? " " : " "; + s << values.at(idx) << Pad(' ', len - values.at(idx).size()) + << "//" << numberSpace << enumValue.toHex(targetHexLen) + << numberSpace << enumValue.toString() << '\n'; + } + s << "};\n" << outdent; + } + + // Build initialization of anonymous enums + if (cppEnum.enumKind() == AnonymousEnum) { + int idx = 0; + for (const auto &enumValue : enumValues) { + const QString mangledName = mangleName(enumValue.name()); + const QString pyValue = initializerValues + u'[' + QString::number(idx++) + u']'; + if (enclosingClass || hasUpperEnclosingClass) { + s << "tpDict.reset(PepType_GetDict(reinterpret_cast<PyTypeObject *>(" + << enclosingObjectVariable << ")));\n" + << "PyDict_SetItemString(tpDict.object(), \"" << mangledName << "\",\n" + << indent << (isSigned ? "PyLong_FromLongLong" : "PyLong_FromUnsignedLongLong") + << "(" << pyValue << "));\n" << outdent; + } else { + s << "PyModule_AddObject(module, \"" << mangledName << "\",\n" << indent + << (isSigned ? "PyLong_FromLongLong" : "PyLong_FromUnsignedLongLong") << "(" + << pyValue << "));\n" << outdent; + } + } + } + + bool etypeUsed = false; + + QString enumVarTypeObj = cpythonTypeNameExtSet(enumTypeEntry); + if (!cppEnum.isAnonymous()) { + int packageLevel = packageName().count(u'.') + 1; + s << "EType = Shiboken::Enum::" + << "createPythonEnum" + << '(' << enclosingObjectVariable << ",\n" << indent + << '"' << packageLevel << ':' << getClassTargetFullName(cppEnum) << "\",\n" + << initializerName << ", " << initializerValues << ");\n" << outdent + << enumVarTypeObj << " = EType;\n"; + etypeUsed = true; + } + + if (cppEnum.typeEntry()->flags()) { + s << "// PYSIDE-1735: Mapping the flags class to the same enum class.\n" + << cpythonTypeNameExtSet(cppEnum.typeEntry()->flags()) << " =\n" + << indent << "EType;\n" << outdent; + } + writeEnumConverterInitialization(s, cppEnum); + + s << "// End of '" << cppEnum.name() << "' enum"; + if (cppEnum.typeEntry()->flags()) + s << "/flags"; + s << ".\n\n"; + + return etypeUsed; +} + +void CppGenerator::writeSignalInitialization(TextStream &s, const AbstractMetaClassCPtr &metaClass) +{ + // Try to check something and print some warnings + const auto &signalFuncs = metaClass->cppSignalFunctions(); + for (const auto &cppSignal : signalFuncs) { + if (cppSignal->declaringClass() != metaClass) + continue; + const AbstractMetaArgumentList &arguments = cppSignal->arguments(); + for (const AbstractMetaArgument &arg : arguments) { + AbstractMetaType metaType = arg.type(); + const QByteArray origType = + QMetaObject::normalizedType(qPrintable(metaType.originalTypeDescription())); + const QByteArray cppSig = + QMetaObject::normalizedType(qPrintable(metaType.cppSignature())); + if ((origType != cppSig) && (!metaType.isFlags())) { + qCWarning(lcShiboken).noquote().nospace() + << "Typedef used on signal " << metaClass->qualifiedCppName() << "::" + << cppSignal->signature(); + } + } + } + + s << "PySide::Signal::registerSignals(pyType, &" << m_gsp + << metaClass->qualifiedCppName() << "::staticMetaObject);\n"; +} + +QString CppGenerator::getSimpleClassInitFunctionName(const AbstractMetaClassCPtr &metaClass) +{ + QString initFunctionName; + // Disambiguate namespaces per module to allow for extending them. + if (metaClass->isNamespace()) + initFunctionName += moduleName(); + initFunctionName += metaClass->qualifiedCppName(); + initFunctionName.replace(u"::"_s, u"_"_s); + return initFunctionName; +} + +QString + CppGenerator::getSimpleClassStaticFieldsInitFunctionName(const AbstractMetaClassCPtr &metaClass) +{ + return u"init_"_s + getSimpleClassInitFunctionName(metaClass) + + u"StaticFields"_s; +} + +QString CppGenerator::getInitFunctionName(const GeneratorContext &context) +{ + return getSimpleClassInitFunctionName(context.metaClass()); +} + +void CppGenerator::writeSignatureStrings(TextStream &s, + const QString &signatures, + const QString &arrayName, + const char *comment) +{ + s << "// The signatures string for the " << comment << ".\n" + << "// Multiple signatures have their index \"n:\" in front.\n" + << "static const char *" << arrayName << "_SignatureStrings[] = {\n" << indent; + const auto lines = QStringView{signatures}.split(u'\n', Qt::SkipEmptyParts); + for (auto line : lines) { + // must anything be escaped? + if (line.contains(u'"') || line.contains(u'\\')) + s << "R\"CPP(" << line << ")CPP\",\n"; + else + s << '"' << line << "\",\n"; + } + s << NULL_PTR << "}; // Sentinel\n" << outdent << '\n'; +} + +// Return the class name for which to invoke the destructor +QString CppGenerator::destructorClassName(const AbstractMetaClassCPtr &metaClass, + const GeneratorContext &classContext) +{ + if (metaClass->isNamespace() || metaClass->hasPrivateDestructor()) + return {}; + if (classContext.forSmartPointer()) + return classContext.effectiveClassName(); + const bool isValue = metaClass->typeEntry()->isValue(); + const bool hasProtectedDestructor = metaClass->hasProtectedDestructor(); + if (((avoidProtectedHack() && hasProtectedDestructor) || isValue) + && classContext.useWrapper()) { + return classContext.wrapperName(); + } + if (avoidProtectedHack() && hasProtectedDestructor) + return {}; // Cannot call (happens with "disable-wrapper"). + return metaClass->qualifiedCppName(); +} + +// Return the base type entries for introduceWrapperType() +static ComplexTypeEntryCList pyBaseTypeEntries(const AbstractMetaClassCPtr &metaClass) +{ + ComplexTypeEntryCList result; + if (metaClass->isNamespace()) { + if (auto extended = metaClass->extendedNamespace()) + result.append(extended->typeEntry()); + return result; + } + + const auto &baseClasses = metaClass->typeSystemBaseClasses(); + for (auto base : baseClasses) { + for (; base != nullptr; base = base->baseClass()) { // Find a type that is not disabled. + const auto ct = base->typeEntry()->codeGeneration(); + if (ct == TypeEntry::GenerateCode || ct == TypeEntry::GenerateForSubclass) + break; + } + result.append(base->typeEntry()); + } + return result; +} + +// Return the base type strings for introduceWrapperType() +QStringList CppGenerator::pyBaseTypes(const AbstractMetaClassCPtr &metaClass) +{ + const auto &baseEntries = pyBaseTypeEntries(metaClass); + QStringList result; + for (const auto &baseEntry : baseEntries) + result.append("reinterpret_cast<PyObject *>("_L1 + cpythonTypeNameExt(baseEntry) + u')'); + if (result.isEmpty()) // no base classes -> SbkObjectType. + result.append(sbkObjectTypeF); + return result; +} + +void CppGenerator::writeInitInheritance(TextStream &s) const +{ + s << "static void " << initInheritanceFunction << "()\n{\n" << indent + << "auto &bm = Shiboken::BindingManager::instance();\n" + << sbkUnusedVariableCast("bm"); + for (const auto &cls : api().classes()){ + auto te = cls->typeEntry(); + if (shouldGenerate(te)) { + const auto &baseEntries = pyBaseTypeEntries(cls); + if (!baseEntries.isEmpty()) { + const QString childTypeInitStruct = typeInitStruct(cls->typeEntry()); + for (const auto &baseEntry : baseEntries) { + s << "bm.addClassInheritance(&" << typeInitStruct(baseEntry) << ",\n" + << Pad(' ', 23) << '&' << childTypeInitStruct << ");\n"; + } + } + } + } + s << outdent << "}\n\n"; +} + +void CppGenerator::writeClassRegister(TextStream &s, + const AbstractMetaClassCPtr &metaClass, + const GeneratorContext &classContext, + const QString &signatures) const +{ + ComplexTypeEntryCPtr classTypeEntry = metaClass->typeEntry(); + + AbstractMetaClassCPtr enc = metaClass->targetLangEnclosingClass(); + QString enclosingObjectVariable = enc ? u"enclosingClass"_s : u"module"_s; + + QString pyTypeName = cpythonTypeName(metaClass); + QString initFunctionName = getInitFunctionName(classContext); + + // PYSIDE-510: Create a signatures string for the introspection feature. + writeSignatureStrings(s, signatures, initFunctionName, "functions"); + s << "PyTypeObject *init_" << initFunctionName + << "(PyObject *" << enclosingObjectVariable << ")\n{\n" << indent; + + const QString globalTypeVarExpr = !classContext.forSmartPointer() + ? cpythonTypeNameExtSet(classTypeEntry) + : cpythonTypeNameExtSet(classContext.preciseType()); + s << "if (" << globalTypeVarExpr << " != nullptr)\n" << indent + << "return " << globalTypeVarExpr << ";\n\n" << outdent; + + // Multiple inheritance + QString pyTypeBasesVariable = chopType(pyTypeName) + u"_Type_bases"_s; + const QStringList pyBases = pyBaseTypes(metaClass); + s << "Shiboken::AutoDecRef " << pyTypeBasesVariable << "(PyTuple_Pack(" + << pyBases.size() << ",\n" << indent; + for (qsizetype i = 0, size = pyBases.size(); i < size; ++i) { + if (i) + s << ",\n"; + s << pyBases.at(i); + } + s << "));\n\n" << outdent; + + // Create type and insert it in the module or enclosing class. + const QString typePtr = u"_"_s + chopType(pyTypeName) + + u"_Type"_s; + + s << typePtr << " = Shiboken::ObjectType::introduceWrapperType(\n" << indent; + // 1:enclosingObject + s << enclosingObjectVariable << ",\n"; + + // 2:typeName + s << "\"" << metaClass->name() << "\",\n"; + + // 3:originalName + s << "\""; + if (!classContext.forSmartPointer()) { + s << metaClass->qualifiedCppName(); + if (classTypeEntry->isObject()) + s << '*'; + } else { + s << classContext.preciseType().cppSignature(); + } + + s << "\",\n"; + // 4:typeSpec + s << '&' << chopType(pyTypeName) << "_spec,\n"; + + // 5:cppObjDtor + QString dtorClassName = destructorClassName(metaClass, classContext); + if (dtorClassName.isEmpty()) + s << "nullptr,\n"; + else + s << "&Shiboken::callCppDestructor< " << globalScopePrefix(classContext) + << dtorClassName << " >,\n"; + + // 7:baseTypes + s << pyTypeBasesVariable << ".object()," << '\n'; + + // 8:wrapperflags + QByteArrayList wrapperFlags; + if (enc) + wrapperFlags.append(QByteArrayLiteral("Shiboken::ObjectType::WrapperFlags::InnerClass")); + if (metaClass->deleteInMainThread()) + wrapperFlags.append(QByteArrayLiteral("Shiboken::ObjectType::WrapperFlags::DeleteInMainThread")); + if (classTypeEntry->isValue()) + wrapperFlags.append("Shiboken::ObjectType::WrapperFlags::Value"_ba); + if (wrapperFlags.isEmpty()) + s << '0'; + else + s << wrapperFlags.join(" | "); + + s << outdent << ");\nauto *pyType = " << pyTypeName << "; // references " + << typePtr << "\n" + << "InitSignatureStrings(pyType, " << initFunctionName << "_SignatureStrings);\n"; + + if (usePySideExtensions() && !classContext.forSmartPointer()) + s << "SbkObjectType_SetPropertyStrings(pyType, " + << chopType(pyTypeName) << "_PropertyStrings);\n"; + s << globalTypeVarExpr << " = pyType;\n\n"; + + // Register conversions for the type. + writeConverterRegister(s, metaClass, classContext); + s << '\n'; + + // class inject-code target/beginning + if (!classTypeEntry->codeSnips().isEmpty()) { + writeClassCodeSnips(s, classTypeEntry->codeSnips(), + TypeSystem::CodeSnipPositionBeginning, TypeSystem::TargetLangCode, + classContext); + s << '\n'; + } + + // Fill multiple inheritance data, if needed. + const auto miClass = getMultipleInheritingClass(metaClass); + if (miClass) { + s << "MultipleInheritanceInitFunction func = "; + if (miClass == metaClass) { + s << multipleInheritanceInitializerFunctionName(miClass) << ";\n"; + } else { + s << "Shiboken::ObjectType::getMultipleInheritanceFunction(" + << cpythonTypeNameExt(miClass->typeEntry()) << ");\n"; + } + s << "Shiboken::ObjectType::setMultipleInheritanceFunction(" + << cpythonTypeName(metaClass) << ", func);\n" + << "Shiboken::ObjectType::setCastFunction(" << cpythonTypeName(metaClass) + << ", &" << cpythonSpecialCastFunctionName(metaClass) << ");\n"; + } + + // Set typediscovery struct or fill the struct of another one + if (needsTypeDiscoveryFunction(metaClass)) { + s << "Shiboken::ObjectType::setTypeDiscoveryFunctionV2(\n" << indent + << cpythonTypeName(metaClass) + << ", &" << cpythonBaseName(metaClass) << "_typeDiscovery);" << outdent << "\n\n"; + } + + AbstractMetaEnumList classEnums = metaClass->enums(); + metaClass->getEnumsFromInvisibleNamespacesToBeGenerated(&classEnums); + + if (!classContext.forSmartPointer() && !classEnums.isEmpty()) + s << "// Pass the ..._EnumFlagInfo to the class.\n" + << "SbkObjectType_SetEnumFlagInfo(pyType, " << chopType(pyTypeName) + << "_EnumFlagInfo);\n\n"; + writeEnumsInitialization(s, classEnums); + + if (metaClass->hasSignals()) + writeSignalInitialization(s, metaClass); + + // class inject-code target/end + if (!classTypeEntry->codeSnips().isEmpty()) { + s << '\n'; + writeClassCodeSnips(s, classTypeEntry->codeSnips(), + TypeSystem::CodeSnipPositionEnd, TypeSystem::TargetLangCode, + classContext); + } + + if (usePySideExtensions()) { + if (avoidProtectedHack() && classContext.useWrapper()) + s << classContext.wrapperName() << "::pysideInitQtMetaTypes();\n"; + else + writeInitQtMetaTypeFunctionBody(s, classContext); + } + + if (usePySideExtensions() && isQObject(metaClass)) { + s << "Shiboken::ObjectType::setSubTypeInitHook(pyType, &PySide::initQObjectSubType);\n" + << "PySide::initDynamicMetaObject(pyType, &" << m_gsp + << metaClass->qualifiedCppName() << "::staticMetaObject, sizeof(" + << (shouldGenerateCppWrapper(metaClass) + ? wrapperName(metaClass) : getFullTypeName(metaClass)) + << "));\n"; + } + + s << "\nreturn pyType;\n" << outdent << "}\n"; +} + +void CppGenerator::writeStaticFieldInitialization(TextStream &s, + const AbstractMetaClassCPtr &metaClass) +{ + // cpythonTypeName == "Sbk_QRhiShaderResourceBinding_Data_TypeF" + QString name = cpythonTypeName(metaClass); + const auto parts = QStringView{name}.split(u'_', Qt::SkipEmptyParts); + if (parts.size() < 4) { + s << "\nPyTypeObject *" << getSimpleClassStaticFieldsInitFunctionName(metaClass) + << "(PyObject *module)\n{\n" << indent + << "auto *obType = PyObject_GetAttrString(module, \"" << metaClass->name() << "\");\n" + << "auto *type = reinterpret_cast<PyTypeObject *>(obType);\n" + << "Shiboken::AutoDecRef dict(PepType_GetDict(type));\n"; + } else { + s << "\nPyTypeObject *" << getSimpleClassStaticFieldsInitFunctionName(metaClass) + << "(PyObject *module)\n{\n" << indent + << "auto *obContainerType = PyObject_GetAttrString(module, \"" + << parts.at(1) << "\");\n" + << "auto *obType = PyObject_GetAttrString(obContainerType, \"" + << parts.at(2) << "\");\n" + << "auto *type = reinterpret_cast<PyTypeObject *>(obType);\n" + << "Shiboken::AutoDecRef dict(PepType_GetDict(type));\n"; + } + for (const AbstractMetaField &field : metaClass->fields()) { + if (field.isStatic()) { + s << "PyDict_SetItemString(dict, \"" << field.name() + << "\",\n "; + writeToPythonConversion(s, field.type(), metaClass, field.qualifiedCppName()); + s << ");\n"; + } + } + s << "return type;\n" << outdent << "}\n"; +} + +enum class QtRegisterMetaType +{ + None, Pointer, Value +}; + +static bool hasQtMetaTypeRegistrationSpec(const AbstractMetaClassCPtr &c) +{ + return c->typeEntry()->qtMetaTypeRegistration() != + TypeSystem::QtMetaTypeRegistration::Unspecified; +} + +// Returns if and how to register the Qt meta type. By default, "pointer" for +// non-QObject object types and "value" for non-abstract, default-constructible +// value types. +QtRegisterMetaType qtMetaTypeRegistration(const AbstractMetaClassCPtr &c) +{ + if (c->isNamespace()) + return QtRegisterMetaType::None; + + // Specified in type system? + const bool isObject = c->isObjectType(); + switch (c->typeEntry()->qtMetaTypeRegistration()) { + case TypeSystem::QtMetaTypeRegistration::Disabled: + return QtRegisterMetaType::None; + case TypeSystem::QtMetaTypeRegistration::Enabled: + case TypeSystem::QtMetaTypeRegistration::BaseEnabled: + return isObject ? QtRegisterMetaType::Pointer : QtRegisterMetaType::Value; + case TypeSystem::QtMetaTypeRegistration::Unspecified: + break; + } + + // Is there a "base" specification in some base class, meaning only the + // base class is to be registered? + if (auto base = recurseClassHierarchy(c, hasQtMetaTypeRegistrationSpec)) { + const auto baseSpec = base->typeEntry()->qtMetaTypeRegistration(); + if (baseSpec == TypeSystem::QtMetaTypeRegistration::BaseEnabled) + return QtRegisterMetaType::None; + } + + // Default. + if (isObject) + return isQObject(c) ? QtRegisterMetaType::None : QtRegisterMetaType::Pointer; + + return !c->isAbstract() && c->isDefaultConstructible() + ? QtRegisterMetaType::Value : QtRegisterMetaType::None; +} + +void CppGenerator::writeInitQtMetaTypeFunctionBody(TextStream &s, const GeneratorContext &context) +{ + const auto metaClass = context.metaClass(); + // Gets all class name variants used on different possible scopes + QStringList nameVariants; + if (!context.forSmartPointer()) + nameVariants << metaClass->name(); + else + nameVariants << context.preciseType().cppSignature(); + + AbstractMetaClassCPtr enclosingClass = metaClass->enclosingClass(); + while (enclosingClass) { + if (enclosingClass->typeEntry()->generateCode()) + nameVariants << (enclosingClass->name() + u"::"_s + nameVariants.constLast()); + enclosingClass = enclosingClass->enclosingClass(); + } + + QString className; + if (!context.forSmartPointer()) + className = metaClass->qualifiedCppName(); + else + className = context.preciseType().cppSignature(); + + // Register meta types for signal/slot connections to work + // Qt metatypes are registered only on their first use, so we do this now. + switch (qtMetaTypeRegistration(metaClass)) { + case QtRegisterMetaType::None: + break; + case QtRegisterMetaType::Pointer: + s << "qRegisterMetaType< " << m_gsp << className << " *>();\n"; + break; + case QtRegisterMetaType::Value: + for (const QString &name : std::as_const(nameVariants)) + s << "qRegisterMetaType< " << m_gsp << className << " >(\"" << name << "\");\n"; + break; + } + + for (const AbstractMetaEnum &metaEnum : metaClass->enums()) { + if (!metaEnum.isPrivate() && !metaEnum.isAnonymous()) { + for (const QString &name : std::as_const(nameVariants)) { + s << "qRegisterMetaType< " << m_gsp + << metaEnum.typeEntry()->qualifiedCppName() << " >(\"" + << name << "::" << metaEnum.name() << "\");\n"; + } + } + } +} + +void CppGenerator::replacePolymorphicIdPlaceHolders(const AbstractMetaClassCPtr &metaClass, + QString *id) +{ + if (id->contains("%1"_L1)) { + QString replacement = " reinterpret_cast< "_L1 + m_gsp + metaClass->qualifiedCppName() + + " *>(cptr)"_L1; + id->replace("%1"_L1, replacement); + } + if (id->contains("%B"_L1)) { + auto baseClass = metaClass; + while (!baseClass->typeEntry()->isPolymorphicBase() && baseClass->baseClass()) + baseClass = baseClass->baseClass(); + QString replacement = " reinterpret_cast< "_L1 + m_gsp + baseClass->qualifiedCppName() + + " *>(cptr)"_L1; + id->replace("%B"_L1, replacement); + } +} + +void CppGenerator::writeTypeDiscoveryFunction(TextStream &s, + const AbstractMetaClassCPtr &metaClass) +{ + QString polymorphicExpr = metaClass->typeEntry()->polymorphicIdValue(); + + s << "static void *" << cpythonBaseName(metaClass) + << "_typeDiscovery(void *cptr, PyTypeObject *instanceType)\n{\n" << indent + << sbkUnusedVariableCast("cptr") + << sbkUnusedVariableCast("instanceType"); + + if (!polymorphicExpr.isEmpty()) { + replacePolymorphicIdPlaceHolders(metaClass, &polymorphicExpr); + s << "if (" << polymorphicExpr << ")\n" << indent + << "return cptr;\n" << outdent; + } else if (metaClass->isPolymorphic()) { + const auto &ancestors = metaClass->allTypeSystemAncestors(); + for (const auto &ancestor : ancestors) { + if (ancestor->baseClass() && !ancestor->typeEntry()->isPolymorphicBase()) + continue; + if (ancestor->isPolymorphic()) { + s << "if (instanceType == Shiboken::SbkType< " << m_gsp + << ancestor->qualifiedCppName() << " >())\n" << indent + << "return dynamic_cast< " << getFullTypeName(metaClass) + << " *>(reinterpret_cast< "<< getFullTypeName(ancestor) + << " *>(cptr));\n" << outdent; + } else { + qCWarning(lcShiboken).noquote().nospace() + << metaClass->qualifiedCppName() << " inherits from a non polymorphic type (" + << ancestor->qualifiedCppName() << "), type discovery based on RTTI is " + "impossible, write a polymorphic-id-expression for this type."; + } + + } + } + s << "return {};\n" << outdent << "}\n\n"; +} + +void CppGenerator::writeSetattroDefinition(TextStream &s, + const AbstractMetaClassCPtr &metaClass) +{ + s << "static int " << ShibokenGenerator::cpythonSetattroFunctionName(metaClass) + << "(PyObject *self, PyObject *name, PyObject *value)\n{\n" << indent; + if (wrapperDiagnostics()) { + s << R"(std::cerr << __FUNCTION__ << ' ' << Shiboken::debugPyObject(name) + << ' ' << Shiboken::debugPyObject(value) << '\n';)" << '\n'; + } +} + +void CppGenerator::writeSetattroDefaultReturn(TextStream &s) +{ + s << "return PyObject_GenericSetAttr(self, name, value);\n" + << outdent << "}\n\n"; +} + +void CppGenerator::writeSetattroFunction(TextStream &s, AttroCheck attroCheck, + const GeneratorContext &context) const +{ + Q_ASSERT(!context.forSmartPointer()); + const auto metaClass = context.metaClass(); + writeSetattroDefinition(s, metaClass); + + // PYSIDE-1019: Switch tp_dict before doing tp_setattro. + if (usePySideExtensions()) + s << "PySide::Feature::Select(self);\n"; + + // PYSIDE-803: Detect duck-punching; clear cache if a method is set. + if (attroCheck.testFlag(AttroCheckFlag::SetattroMethodOverride) + && context.useWrapper()) { + s << "if (value != nullptr && PyCallable_Check(value) != 0) {\n" << indent + << "auto plain_inst = " << cpythonWrapperCPtr(metaClass, PYTHON_SELF_VAR) << ";\n" + << "auto *inst = dynamic_cast<" << context.wrapperName() << " *>(plain_inst);\n" + << "if (inst != nullptr)\n" << indent + << "inst->resetPyMethodCache();\n" << outdent << outdent + << "}\n"; + } + if (attroCheck.testFlag(AttroCheckFlag::SetattroQObject)) { + s << "Shiboken::AutoDecRef pp(reinterpret_cast<PyObject *>(PySide::Property::getObject(self, name)));\n" + << "if (!pp.isNull())\n" << indent + << "return PySide::Property::setValue(reinterpret_cast<PySideProperty *>(pp.object()), self, value);\n" + << outdent; + } + + if (attroCheck.testFlag(AttroCheckFlag::SetattroUser)) { + auto func = AbstractMetaClass::queryFirstFunction(metaClass->functions(), + FunctionQueryOption::SetAttroFunction); + Q_ASSERT(func); + s << "{\n" << indent + << "auto " << CPP_SELF_VAR << " = " + << cpythonWrapperCPtr(metaClass, PYTHON_SELF_VAR) << ";\n"; + writeClassCodeSnips(s, func->injectedCodeSnips(), TypeSystem::CodeSnipPositionAny, + TypeSystem::TargetLangCode, context); + s << outdent << "}\n"; + } + + writeSetattroDefaultReturn(s); +} + +void CppGenerator::writeGetattroDefinition(TextStream &s, const AbstractMetaClassCPtr &metaClass) +{ + s << "static PyObject *" << cpythonGetattroFunctionName(metaClass) + << "(PyObject *self, PyObject *name)\n{\n" << indent; +} + +QString CppGenerator::qObjectGetAttroFunction() const +{ + static QString result; + if (result.isEmpty()) { + auto qobjectClass = AbstractMetaClass::findClass(api().classes(), qObjectT); + Q_ASSERT(qobjectClass); + result = u"PySide::getHiddenDataFromQObject("_s + + cpythonWrapperCPtr(qobjectClass, PYTHON_SELF_VAR) + + u", self, name)"_s; + } + return result; +} + +void CppGenerator::writeGetattroFunction(TextStream &s, AttroCheck attroCheck, + const GeneratorContext &context) const +{ + Q_ASSERT(!context.forSmartPointer()); + const auto metaClass = context.metaClass(); + writeGetattroDefinition(s, metaClass); + + // PYSIDE-1019: Switch tp_dict before doing tp_getattro. + if (usePySideExtensions()) + s << "PySide::Feature::Select(self);\n"; + + const QString getattrFunc = usePySideExtensions() && isQObject(metaClass) + ? qObjectGetAttroFunction() : u"PyObject_GenericGetAttr(self, name)"_s; + + if (attroCheck.testFlag(AttroCheckFlag::GetattroOverloads)) { + s << "// Search the method in the instance dict\n" + << "auto *ob_dict = SbkObject_GetDict_NoRef(self);\n"; + s << "if (auto *meth = PyDict_GetItem(ob_dict, name)) {\n" << indent + << "Py_INCREF(meth);\nreturn meth;\n" << outdent << "}\n" + << "// Search the method in the type dict\n" + << "if (Shiboken::Object::isUserType(self)) {\n" << indent; + // PYSIDE-772: Perform optimized name mangling. + s << "Shiboken::AutoDecRef tmp(_Pep_PrivateMangle(self, name));\n" + << "Shiboken::AutoDecRef tpDict(PepType_GetDict(Py_TYPE(self)));\n" + << "if (auto *meth = PyDict_GetItem(tpDict.object(), tmp)) {\n" << indent; + // PYSIDE-1523: PyFunction_Check is not accepting compiled functions. + s << "if (std::strcmp(Py_TYPE(meth)->tp_name, \"compiled_function\") == 0) {\n" << indent + << "auto descrGetFunc = " + << pyTypeGetSlot("descrgetfunc", "Py_TYPE(meth)", "Py_tp_descr_get") << ";\n" + << "return descrGetFunc(meth, self, nullptr);\n" << outdent + << "}\nreturn PyFunction_Check(meth) ? PyMethod_New(meth, self)\n" + << " : " << getattrFunc << ";\n" << outdent + << "}\n" << outdent << "}\n"; + + const auto &funcs = getMethodsWithBothStaticAndNonStaticMethods(metaClass); + for (const auto &func : funcs) { + QString defName = cpythonMethodDefinitionName(func); + s << "static PyMethodDef non_static_" << defName << " = {\n" << indent + << defName << ".ml_name,\n" + << defName << ".ml_meth,\n" + << defName << ".ml_flags & (~METH_STATIC),\n" + << defName << ".ml_doc,\n" << outdent + << "};\n" + << "if (Shiboken::String::compare(name, \"" + << func->definitionNames().constFirst() << "\") == 0)\n" << indent + << "return PyCFunction_NewEx(&non_static_" << defName << ", self, 0);\n" + << outdent; + } + } + + if (attroCheck.testFlag(AttroCheckFlag::GetattroUser)) { + auto func = AbstractMetaClass::queryFirstFunction(metaClass->functions(), + FunctionQueryOption::GetAttroFunction); + Q_ASSERT(func); + s << "{\n" << indent + << "auto " << CPP_SELF_VAR << " = " + << cpythonWrapperCPtr(metaClass, PYTHON_SELF_VAR) << ";\n"; + writeClassCodeSnips(s, func->injectedCodeSnips(), TypeSystem::CodeSnipPositionAny, + TypeSystem::TargetLangCode, context); + s << outdent << "}\n"; + } + + s << "return " << getattrFunc << ";\n" << outdent << "}\n\n"; +} + +void CppGenerator::writeNbBoolExpression(TextStream &s, const BoolCastFunction &f, + bool invert) +{ + if (f.function->isOperatorBool()) { + if (invert) + s << '!'; + s << '*' << CPP_SELF_VAR; + return; + } + if (invert != f.invert) + s << '!'; + s << CPP_SELF_VAR << "->" << f.function->name() << "()"; +} + +void CppGenerator::writeNbBoolFunction(const GeneratorContext &context, + const BoolCastFunction &f, + TextStream &s) +{ + s << "static int " << cpythonBaseName(context.metaClass()) << "___nb_bool(PyObject *self)\n" + << "{\n" << indent; + writeCppSelfDefinition(s, context, ErrorReturn::MinusOne); + + const bool allowThread = f.function->allowThread(); + if (allowThread) + s << "int result;\n" << BEGIN_ALLOW_THREADS << "\nresult = "; + else + s << "return "; + + writeNbBoolExpression(s, f); + s << " ? 1 : 0;\n"; + + if (allowThread) + s << END_ALLOW_THREADS << "\nreturn result;\n"; + s << outdent << "}\n\n"; +} + +// Write declaration and invocation of the init function for the module init +// function. +void CppGenerator::writeInitFunc(TextStream &declStr, TextStream &callStr, + const QString &initFunctionName, + const TypeEntryCPtr &enclosingEntry, + const QString &pythonName, bool lazy) +{ + const QString functionName = "init_"_L1 + initFunctionName; + const bool hasParent = enclosingEntry && enclosingEntry->type() != TypeEntry::TypeSystemType; + declStr << "PyTypeObject *" << functionName << "(PyObject *" + << (hasParent ? "enclosingClass" : "module") << ");\n"; + + if (!lazy) { + const QString enclosing = hasParent + ? "reinterpret_cast<PyObject *>("_L1 + cpythonTypeNameExt(enclosingEntry) + u')' + : "module"_L1; + callStr << functionName << '(' << enclosing << ");\n"; + } else if (hasParent) { + const QString &enclosingName = enclosingEntry->name(); + const auto parts = QStringView{enclosingName}.split(u"::", Qt::SkipEmptyParts); + const QString namePathPrefix = enclosingEntry->name().replace("::"_L1, "."_L1); + callStr << "Shiboken::Module::AddTypeCreationFunction(" + << "module, \"" << parts[0] << "\", " + << functionName << ", \"" << namePathPrefix << '.' << pythonName << "\");\n"; + } else { + callStr << "Shiboken::Module::AddTypeCreationFunction(" + << "module, \"" << pythonName << "\", " + << functionName << ");\n"; + } +} + +static void writeSubModuleHandling(TextStream &s, const QString &moduleName, + const QString &subModuleOf) +{ + s << "{\n" << indent + << "Shiboken::AutoDecRef parentModule(Shiboken::Module::import(\"" + << subModuleOf << "\"));\n" + << "if (parentModule.isNull())\n" << indent + << "return nullptr;\n" << outdent + << "if (PyModule_AddObject(parentModule.object(), \"" << moduleName + << "\", module) < 0)\n" + << indent << "return nullptr;\n" << outdent << outdent << "}\n"; +} + +bool CppGenerator::finishGeneration() +{ + //Generate CPython wrapper file + StringStream s_classInitDecl(TextStream::Language::Cpp); + StringStream s_classPythonDefines(TextStream::Language::Cpp); + + std::set<Include> includes; + StringStream s_globalFunctionImpl(TextStream::Language::Cpp); + StringStream s_globalFunctionDef(TextStream::Language::Cpp); + StringStream signatureStream(TextStream::Language::Cpp); + + const auto functionGroups = getGlobalFunctionGroups(); + for (auto it = functionGroups.cbegin(), end = functionGroups.cend(); it != end; ++it) { + const AbstractMetaFunctionCList &overloads = it.value(); + for (const auto &func : overloads) { + if (auto te = func->typeEntry()) + includes.insert(te->include()); + } + + if (overloads.isEmpty()) + continue; + + // Dummy context to satisfy the API. + GeneratorContext classContext; + OverloadData overloadData(overloads, api()); + + writeMethodWrapper(s_globalFunctionImpl, overloadData, classContext); + writeSignatureInfo(signatureStream, overloadData); + s_globalFunctionDef << methodDefinitionEntries(overloadData); + } + + AbstractMetaClassCList classesWithStaticFields; + for (const auto &cls : api().classes()){ + auto te = cls->typeEntry(); + if (shouldGenerate(te)) { + const bool hasConfigCondition = te->hasConfigCondition(); + if (hasConfigCondition) { + s_classInitDecl << te->configCondition() << '\n'; + s_classPythonDefines << te->configCondition() << '\n'; + } + writeInitFunc(s_classInitDecl, s_classPythonDefines, + getSimpleClassInitFunctionName(cls), + targetLangEnclosingEntry(te), cls->name()); + if (cls->hasStaticFields()) { + s_classInitDecl << "PyTypeObject *" + << getSimpleClassStaticFieldsInitFunctionName(cls) << "(PyObject *module);\n"; + classesWithStaticFields.append(cls); + } + if (hasConfigCondition) { + s_classInitDecl << "#endif\n"; + s_classPythonDefines << "#endif\n"; + } + } + } + + // Initialize smart pointer types. + for (const auto &smp : api().instantiatedSmartPointers()) { + GeneratorContext context = contextForSmartPointer(smp.specialized, smp.type); + const auto enclosingClass = context.metaClass()->enclosingClass(); + auto enclosingTypeEntry = enclosingClass + ? enclosingClass->typeEntry() + : targetLangEnclosingEntry(smp.type.typeEntry()); + + writeInitFunc(s_classInitDecl, s_classPythonDefines, + getInitFunctionName(context), + enclosingTypeEntry, smp.type.name()); + includes.insert(smp.type.instantiations().constFirst().typeEntry()->include()); + } + + for (auto &instantiatedContainer : api().instantiatedContainers()) { + includes.insert(instantiatedContainer.typeEntry()->include()); + for (const auto &inst : instantiatedContainer.instantiations()) + includes.insert(inst.typeEntry()->include()); + } + + const ExtendedConverterData extendedConverters = getExtendedConverters(); + for (auto it = extendedConverters.cbegin(), end = extendedConverters.cend(); it != end; ++it) { + TypeEntryCPtr te = it.key(); + includes.insert(te->include()); + for (const auto &metaClass : it.value()) + includes.insert(metaClass->typeEntry()->include()); + } + + const QList<CustomConversionPtr> &typeConversions = getPrimitiveCustomConversions(); + for (const auto &c : typeConversions) { + if (auto te = c->ownerType()) + includes.insert(te->include()); + } + + QString moduleFileName(outputDirectory() + u'/' + subDirectoryForPackage(packageName())); + moduleFileName += u'/' + moduleName().toLower() + u"_module_wrapper.cpp"_s; + + FileOut file(moduleFileName); + + TextStream &s = file.stream; + s.setLanguage(TextStream::Language::Cpp); + + // write license comment + s << licenseComment() << R"( +#include <sbkpython.h> +#include <shiboken.h> +#include <algorithm> +#include <signature.h> +)"; + + if (!api().instantiatedContainers().isEmpty()) + s << "#include <sbkcontainer.h>\n#include <sbkstaticstrings.h>\n"; + + if (usePySideExtensions()) { + s << includeQDebug; + s << R"(#include <pysidecleanup.h> +#include <pysideqenum.h> +#include <feature_select.h> +#include <pysidestaticstrings.h> +)"; + } + + s << "#include \"" << getModuleHeaderFileName() << '"' << "\n\n"; + for (const Include &include : includes) + s << include; + s << '\n'; + + // Global enums + AbstractMetaEnumList globalEnums = api().globalEnums(); + for (const auto &nsp : invisibleTopNamespaces()) { + const auto oldSize = globalEnums.size(); + nsp->getEnumsToBeGenerated(&globalEnums); + if (globalEnums.size() > oldSize) + s << nsp->typeEntry()->include(); + } + + TypeDatabase *typeDb = TypeDatabase::instance(); + TypeSystemTypeEntryCPtr moduleEntry = typeDb->defaultTypeSystemType(); + Q_ASSERT(moduleEntry); + + s << '\n'; + // Extra includes + QList<Include> extraIncludes = moduleEntry->extraIncludes(); + for (const AbstractMetaEnum &cppEnum : std::as_const(globalEnums)) + extraIncludes.append(cppEnum.typeEntry()->extraIncludes()); + if (!extraIncludes.isEmpty()) { + s << "// Extra includes\n"; + std::sort(extraIncludes.begin(), extraIncludes.end()); + for (const Include &inc : std::as_const(extraIncludes)) + s << inc; + s << '\n'; + } + + // FIXME PYSIDE-7: Remove backwards compatible structure + s << "// Current module's type array.\n" + << "Shiboken::Module::TypeInitStruct *" << cppApiVariableName() << " = nullptr;\n" + << "// Backwards compatible structure with identical indexing.\n" + << "PyTypeObject **" << cppApiVariableNameOld() << " = nullptr;\n" + << "// Current module's PyObject pointer.\n" + << "PyObject *" << pythonModuleObjectName() << " = nullptr;\n" + << "// Current module's converter array.\n" + << "SbkConverter **" << convertersVariableName() << " = nullptr;\n\n"; + + const CodeSnipList snips = moduleEntry->codeSnips(); + + // module inject-code native/beginning + writeModuleCodeSnips(s, snips, TypeSystem::CodeSnipPositionBeginning, TypeSystem::NativeCode); + + // cleanup staticMetaObject attribute + if (usePySideExtensions()) { + QString iType = cppApiVariableName() + "[i].type"_L1; + QString iName = cppApiVariableName() + "[i].fullName"_L1; + + s << "void cleanTypesAttributes() {\n" << indent + << "static PyObject *attrName = Shiboken::PyName::qtStaticMetaObject();\n" + << "const int imax = SBK_" << moduleName() << "_IDX_COUNT;\n" + << "for (int i = 0; i < imax && " << iName << " != nullptr; ++i) {\n" << indent + << "auto *pyType = reinterpret_cast<PyObject *>(" << iType << ");\n" + << "if (pyType != nullptr && PyObject_HasAttr(pyType, attrName))\n" << indent + << "PyObject_SetAttr(pyType, attrName, Py_None);\n" << outdent + << outdent << "}\n" << outdent << "}\n\n"; + } + + s << "// Global functions " + << "------------------------------------------------------------\n" + << s_globalFunctionImpl.toString() << '\n' + << "static PyMethodDef " << moduleName() << "_methods[] = {\n" << indent + << s_globalFunctionDef.toString() + << METHOD_DEF_SENTINEL << outdent << "};\n\n" + << "// Classes initialization functions " + << "------------------------------------------------------------\n" + << s_classInitDecl.toString() << '\n'; + + if (!globalEnums.isEmpty()) { + StringStream convImpl(TextStream::Language::Cpp); + + s << "// Enum definitions " + << "------------------------------------------------------------\n"; + for (const AbstractMetaEnum &cppEnum : std::as_const(globalEnums)) + writeEnumConverterFunctions(s, cppEnum); + + if (convImpl.size() > 0) { + s << "// Enum converters " + << "------------------------------------------------------------\n" + << "namespace Shiboken\n{\n" + << convImpl.toString() << '\n' + << "} // namespace Shiboken\n\n"; + } + + s << '\n'; + } + + const QStringList &requiredModules = typeDb->requiredTargetImports(); + if (!requiredModules.isEmpty()) + s << "// Required modules' type and converter arrays.\n"; + for (const QString &requiredModule : requiredModules) { + s << "Shiboken::Module::TypeInitStruct *" << cppApiVariableName(requiredModule) << ";\n" + << "SbkConverter **" << convertersVariableName(requiredModule) << ";\n"; + } + + s << "\n// Module initialization " + << "------------------------------------------------------------\n"; + if (!extendedConverters.isEmpty()) { + s << '\n' << "// Extended Converters.\n\n"; + for (ExtendedConverterData::const_iterator it = extendedConverters.cbegin(), end = extendedConverters.cend(); it != end; ++it) { + TypeEntryCPtr externalType = it.key(); + s << "// Extended implicit conversions for " + << externalType->qualifiedTargetLangName() << '.' << '\n'; + for (const auto &sourceClass : it.value()) { + AbstractMetaType sourceType = AbstractMetaType::fromAbstractMetaClass(sourceClass); + AbstractMetaType targetType = AbstractMetaType::fromTypeEntry(externalType); + writePythonToCppConversionFunctions(s, sourceType, targetType); + } + } + } + + if (!typeConversions.isEmpty()) { + s << "\n// Primitive Type converters.\n\n"; + for (const auto &conversion : typeConversions) { + s << "// C++ to Python conversion for primitive type '" << conversion->ownerType()->qualifiedCppName() << "'.\n"; + writeCppToPythonFunction(s, conversion); + writeCustomConverterFunctions(s, conversion); + } + s << '\n'; + } + + QHash<AbstractMetaType, OpaqueContainerData> opaqueContainers; + const auto &containers = api().instantiatedContainers(); + QSet<AbstractMetaType> valueConverters; + if (!containers.isEmpty()) { + s << "// Container Type converters.\n\n"; + for (const AbstractMetaType &container : containers) { + s << "// C++ to Python conversion for container type '" + << container.cppSignature() << "'.\n"; + writeContainerConverterFunctions(s, container); + if (container.generateOpaqueContainer()) { + auto data = writeOpaqueContainerConverterFunctions(s, container, + &valueConverters); + opaqueContainers.insert(container, data); + } + } + s << '\n'; + } + + // Implicit smart pointers conversions + const auto &smartPointersList = api().instantiatedSmartPointers(); + if (!smartPointersList.isEmpty()) { + s << "// SmartPointers converters.\n\n"; + for (const auto &smp : smartPointersList) { + s << "// C++ to Python conversion for smart pointer type '" + << smp.type.cppSignature() << "'.\n"; + writeSmartPointerConverterFunctions(s, smp.type); + } + s << '\n'; + } + + s << "static struct PyModuleDef moduledef = {\n" + << " /* m_base */ PyModuleDef_HEAD_INIT,\n" + << " /* m_name */ \"" << moduleName() << "\",\n" + << " /* m_doc */ nullptr,\n" + << " /* m_size */ -1,\n" + << " /* m_methods */ " << moduleName() << "_methods,\n" + << " /* m_reload */ nullptr,\n" + << " /* m_traverse */ nullptr,\n" + << " /* m_clear */ nullptr,\n" + << " /* m_free */ nullptr\n};\n\n"; + + // PYSIDE-510: Create a signatures string for the introspection feature. + writeSignatureStrings(s, signatureStream.toString(), moduleName(), "global functions"); + + writeInitInheritance(s); + + // Write module init function + const QString globalModuleVar = pythonModuleObjectName(); + s << "extern \"C\" LIBSHIBOKEN_EXPORT PyObject *PyInit_" + << moduleName() << "()\n{\n" << indent; + // Guard against repeated invocation + s << "if (" << globalModuleVar << " != nullptr)\n" + << indent << "return " << globalModuleVar << ";\n" << outdent; + + // module inject-code target/beginning + writeModuleCodeSnips(s, snips, TypeSystem::CodeSnipPositionBeginning, + TypeSystem::TargetLangCode); + + for (const QString &requiredModule : requiredModules) { + s << "{\n" << indent + << "Shiboken::AutoDecRef requiredModule(Shiboken::Module::import(\"" << requiredModule << "\"));\n" + << "if (requiredModule.isNull())\n" << indent + << "return nullptr;\n" << outdent + << cppApiVariableName(requiredModule) + << " = Shiboken::Module::getTypes(requiredModule);\n" + << convertersVariableName(requiredModule) + << " = Shiboken::Module::getTypeConverters(requiredModule);\n" << outdent + << "}\n\n"; + } + + int maxTypeIndex = getMaxTypeIndex() + api().instantiatedSmartPointers().size(); + if (maxTypeIndex) { + s << "// Create an array of wrapper types/names for the current module.\n" + << "static Shiboken::Module::TypeInitStruct cppApi[] = {\n" << indent; + + // Windows did not like an array of QString. + QStringList typeNames; + for (int idx = 0; idx < maxTypeIndex; ++idx) + typeNames.append("+++ unknown entry #"_L1 + QString::number(idx) + + " in "_L1 + moduleName()); + + collectFullTypeNamesArray(typeNames); + + for (auto typeName : typeNames) + s << "{nullptr, \"" << typeName << "\"},\n"; + + s << "{nullptr, nullptr}\n" << outdent << "};\n" + << "// The new global structure consisting of (type, name) pairs.\n" + << cppApiVariableName() << " = cppApi;\n"; + if (usePySideExtensions()) + s << "QT_WARNING_PUSH\nQT_WARNING_DISABLE_DEPRECATED\n"; + s << "// The backward compatible alias with upper case indexes.\n" + << cppApiVariableNameOld() << " = reinterpret_cast<PyTypeObject **>(cppApi);\n"; + if (usePySideExtensions()) + s << "QT_WARNING_POP\n"; + s << '\n'; + } + + s << "// Create an array of primitive type converters for the current module.\n" + << "static SbkConverter *sbkConverters[SBK_" << moduleName() + << "_CONVERTERS_IDX_COUNT" << "];\n" + << convertersVariableName() << " = sbkConverters;\n\n" + << "PyObject *module = Shiboken::Module::create(\"" << moduleName() + << "\", &moduledef);\n\n" + << "// Make module available from global scope\n" + << globalModuleVar << " = module;\n\n"; + + const QString subModuleOf = typeDb->defaultTypeSystemType()->subModuleOf(); + if (!subModuleOf.isEmpty()) + writeSubModuleHandling(s, moduleName(), subModuleOf); + + s << "// Initialize classes in the type system\n" + << s_classPythonDefines.toString(); + + if (!typeConversions.isEmpty()) { + s << '\n'; + for (const auto &conversion : typeConversions) { + writePrimitiveConverterInitialization(s, conversion); + s << '\n'; + } + } + + if (!containers.isEmpty()) { + s << '\n'; + for (const AbstractMetaType &container : containers) { + const QString converterObj = writeContainerConverterInitialization(s, container, api()); + const auto it = opaqueContainers.constFind(container); + if (it != opaqueContainers.constEnd()) { + writeSetPythonToCppPointerConversion(s, converterObj, + it.value().pythonToConverterFunctionName, + it.value().converterCheckFunctionName); + } + s << '\n'; + } + } + + if (!opaqueContainers.isEmpty()) { + s << "\n// Opaque container type registration\n" + << "PyObject *ob_type{};\n"; + for (const auto &d : opaqueContainers) + s << d.registrationCode; + s << '\n'; + } + + if (!smartPointersList.isEmpty()) { + s << '\n'; + for (const auto &smp : smartPointersList) { + writeSmartPointerConverterInitialization(s, smp.type); + s << '\n'; + } + } + + if (!extendedConverters.isEmpty()) { + s << '\n'; + for (ExtendedConverterData::const_iterator it = extendedConverters.cbegin(), end = extendedConverters.cend(); it != end; ++it) { + writeExtendedConverterInitialization(s, it.key(), it.value()); + s << '\n'; + } + } + + writeEnumsInitialization(s, globalEnums); + + s << "// Register primitive types converters.\n"; + const PrimitiveTypeEntryCList &primitiveTypeList = primitiveTypes(); + for (const auto &pte : primitiveTypeList) { + if (!pte->generateCode() || !isCppPrimitive(pte)) + continue; + if (!pte->referencesType()) + continue; + TypeEntryCPtr referencedType = basicReferencedTypeEntry(pte); + registerConverterInScopes(s, pte->qualifiedCppName(), converterObject(referencedType)); + } + + s << '\n'; + if (maxTypeIndex) + s << "Shiboken::Module::registerTypes(module, " << cppApiVariableName() << ");\n"; + s << "Shiboken::Module::registerTypeConverters(module, " << convertersVariableName() << ");\n"; + + // Static fields are registered last since they may use converter functions + // of the previously registered types (PYSIDE-1529). + if (!classesWithStaticFields.isEmpty()) { + s << "\n// Static field initialization\n"; + for (const auto &cls : std::as_const(classesWithStaticFields)) { + ConfigurableScope configScope(s, cls->typeEntry()); + s << getSimpleClassStaticFieldsInitFunctionName(cls) << "(module);\n"; + } + } + + s << '\n' << initInheritanceFunction << "();\n" + << "\nif (" << shibokenErrorsOccurred << ") {\n" << indent + << "PyErr_Print();\n" + << "Py_FatalError(\"can't initialize module " << moduleName() << "\");\n" + << outdent << "}\n"; + + // module inject-code target/end + writeModuleCodeSnips(s, snips, TypeSystem::CodeSnipPositionEnd, TypeSystem::TargetLangCode); + + // module inject-code native/end + writeModuleCodeSnips(s, snips, TypeSystem::CodeSnipPositionEnd, TypeSystem::NativeCode); + + if (usePySideExtensions()) { + for (const AbstractMetaEnum &metaEnum : std::as_const(globalEnums)) + if (!metaEnum.isAnonymous()) { + ConfigurableScope configScope(s, metaEnum.typeEntry()); + s << "qRegisterMetaType< " << getFullTypeName(metaEnum.typeEntry()) + << " >(\"" << metaEnum.name() << "\");\n"; + } + + // cleanup staticMetaObject attribute + s << "PySide::registerCleanupFunction(cleanTypesAttributes);\n\n"; + } + + // finish the rest of get_signature() initialization. + s << "FinishSignatureInitialization(module, " << moduleName() + << "_SignatureStrings);\n" + << "\nreturn module;\n" << outdent << "}\n"; + + file.done(); + return true; +} + +static ArgumentOwner getArgumentOwner(const AbstractMetaFunctionCPtr &func, int argIndex) +{ + ArgumentOwner argOwner = func->argumentOwner(func->ownerClass(), argIndex); + if (argOwner.index == ArgumentOwner::InvalidIndex) + argOwner = func->argumentOwner(func->declaringClass(), argIndex); + return argOwner; +} + +// Whether to enable parent ownership heuristic for a function and its argument. +// Both must belong to the same class hierarchy and have the same +// type entry enabling parent management. +static bool useParentHeuristics(const ApiExtractorResult &api, + const AbstractMetaFunctionCPtr &func, + const AbstractMetaType &argType) +{ + if (!ComplexTypeEntry::isParentManagementEnabled()) // FIXME PYSIDE 7: Remove this + return true; + const auto owner = func->ownerClass(); + if (!owner) + return false; + auto ownerEntry = parentManagementEntry(owner); + if (!ownerEntry) + return false; + auto argTypeEntry = argType.typeEntry(); + if (!argTypeEntry->isComplex()) + return false; + const auto argClass = AbstractMetaClass::findClass(api.classes(), argTypeEntry); + return argClass && parentManagementEntry(argClass) == ownerEntry; +} + +bool CppGenerator::writeParentChildManagement(TextStream &s, const AbstractMetaFunctionCPtr &func, + int argIndex, + bool usePyArgs, bool useHeuristicPolicy) const +{ + const int numArgs = func->arguments().size(); + bool ctorHeuristicEnabled = func->isConstructor() && useCtorHeuristic() && useHeuristicPolicy; + bool heuristicTriggered = false; + + ArgumentOwner argOwner = getArgumentOwner(func, argIndex); + ArgumentOwner::Action action = argOwner.action; + int parentIndex = argOwner.index; + int childIndex = argIndex; + if (ctorHeuristicEnabled && argIndex > 0 && argIndex <= numArgs) { + const AbstractMetaArgument &arg = func->arguments().at(argIndex-1); + if (arg.name() == u"parent" && arg.type().isObjectType() + && useParentHeuristics(api(), func, arg.type())) { + action = ArgumentOwner::Add; + parentIndex = argIndex; + childIndex = -1; + heuristicTriggered = true; + } + } + + QString parentVariable; + QString childVariable; + if (action != ArgumentOwner::Invalid) { + if (!usePyArgs && argIndex > 1) + qCWarning(lcShiboken).noquote().nospace() + << "Argument index for parent tag out of bounds: " << func->signature(); + + if (action == ArgumentOwner::Remove) { + parentVariable = u"Py_None"_s; + } else { + if (parentIndex == 0) { + parentVariable = PYTHON_RETURN_VAR; + } else if (parentIndex == -1) { + parentVariable = PYTHON_SELF_VAR; + } else { + parentVariable = usePyArgs + ? pythonArgsAt(parentIndex - 1) : PYTHON_ARG; + } + } + + if (childIndex == 0) { + childVariable = PYTHON_RETURN_VAR; + } else if (childIndex == -1) { + childVariable = PYTHON_SELF_VAR; + } else { + childVariable = usePyArgs + ? pythonArgsAt(childIndex - 1) : PYTHON_ARG; + } + + s << "// Ownership transferences"; + if (heuristicTriggered) + s << " (constructor heuristics)"; + s << ".\nShiboken::Object::setParent(" << parentVariable << ", " + << childVariable << ");\n"; + return true; + } + + return false; +} + +void CppGenerator::writeParentChildManagement(TextStream &s, const AbstractMetaFunctionCPtr &func, + bool usesPyArgs, + bool useHeuristicForReturn) const +{ + const int numArgs = func->arguments().size(); + + // -1 = return value + // 0 = self + // 1..n = func. args. + for (int i = -1; i <= numArgs; ++i) + writeParentChildManagement(s, func, i, usesPyArgs, useHeuristicForReturn); + + if (useHeuristicForReturn) + writeReturnValueHeuristics(s, func); +} + +void CppGenerator::writeReturnValueHeuristics(TextStream &s, const AbstractMetaFunctionCPtr &func) const +{ + const AbstractMetaType &type = func->type(); + if (!useReturnValueHeuristic() + || !func->ownerClass() + || type.isVoid() + || func->isStatic() + || func->isConstructor() + || func->isTypeModified() + || !useParentHeuristics(api(), func, type) + // Something like parent(), parentWidget(): No child relationship here. + || (func->maybeAccessor() && func->name().startsWith(u"parent"))) { + return; + } + + ArgumentOwner argOwner = getArgumentOwner(func, ArgumentOwner::ReturnIndex); + if (argOwner.action == ArgumentOwner::Invalid || argOwner.index != ArgumentOwner::ThisIndex) { + if (type.isPointerToWrapperType()) { + s << "// Ownership transferences (return value heuristics).\n" + << "Shiboken::Object::setParent(self, " << PYTHON_RETURN_VAR << ");\n"; + } + } +} + +void CppGenerator::writeHashFunction(TextStream &s, const GeneratorContext &context) +{ + const auto metaClass = context.metaClass(); + const char hashType[] = "Py_hash_t"; + s << "static " << hashType << ' ' << cpythonBaseName(metaClass) + << "_HashFunc(PyObject *self)\n{\n" << indent; + writeCppSelfDefinition(s, context); + + bool deref = true; + QString name = metaClass->typeEntry()->hashFunction(); + if (name.isEmpty()) + name = metaClass->hashFunction(); + else + deref = !metaClass->isObjectType(); + Q_ASSERT(!name.isEmpty()); + + s << "return " << hashType << '(' << name << '('; + if (deref) + s << '*'; + s << CPP_SELF_VAR << "));\n" + << outdent << "}\n\n"; +} + +void CppGenerator::writeDefaultSequenceMethods(TextStream &s, + const GeneratorContext &context) const +{ + const auto metaClass = context.metaClass(); + ErrorReturn errorReturn = ErrorReturn::Zero; + + // __len__ + const QString namePrefix = cpythonBaseName(metaClass->typeEntry()); + s << "Py_ssize_t " << namePrefix + << "__len__(PyObject *self)\n{\n" << indent; + writeCppSelfDefinition(s, context, errorReturn); + s << "return " << CPP_SELF_VAR << "->size();\n" + << outdent << "}\n"; + + // __getitem__ + s << "PyObject *" << namePrefix + << "__getitem__(PyObject *self, Py_ssize_t _i)\n{\n" << indent; + writeCppSelfDefinition(s, context, errorReturn); + writeIndexError(s, u"index out of bounds"_s, errorReturn); + + s << metaClass->qualifiedCppName() << "::const_iterator _item = " + << CPP_SELF_VAR << "->begin();\n" + << "std::advance(_item, _i);\n"; + + const AbstractMetaTypeList &instantiations = metaClass->templateBaseClassInstantiations(); + if (instantiations.isEmpty()) { + QString m; + QTextStream(&m) << "shiboken: " << __FUNCTION__ + << ": Internal error, no instantiations of \"" << metaClass->qualifiedCppName() + << "\" were found."; + throw Exception(m); + } + const AbstractMetaType &itemType = instantiations.constFirst(); + + s << "return "; + writeToPythonConversion(s, itemType, metaClass, u"*_item"_s); + s << ";\n" << outdent << "}\n"; + + // __setitem__ + s << "int " << namePrefix + << "__setitem__(PyObject *self, Py_ssize_t _i, PyObject *pyArg)\n{\n" + << indent; + errorReturn = ErrorReturn::MinusOne; + writeCppSelfDefinition(s, context, errorReturn); + writeIndexError(s, u"list assignment index out of range"_s, errorReturn); + + s << PYTHON_TO_CPPCONVERSION_STRUCT << ' ' << PYTHON_TO_CPP_VAR << ";\n" + << "if (!"; + writeTypeCheck(s, itemType, u"pyArg"_s, isNumber(itemType.typeEntry())); + s << ") {\n" << indent + << "Shiboken::Errors::setSequenceTypeError(\"" << itemType.name() << "\");\n" + << "return -1;\n" << outdent << "}\n"; + writeArgumentConversion(s, itemType, u"cppValue"_s, + u"pyArg"_s, errorReturn, metaClass); + + s << metaClass->qualifiedCppName() << "::iterator _item = " + << CPP_SELF_VAR << "->begin();\n" + << "std::advance(_item, _i);\n" + << "*_item = cppValue;\n"; + + s << "return {};\n" << outdent << "}\n"; +} +void CppGenerator::writeIndexError(TextStream &s, const QString &errorMsg, + ErrorReturn errorReturn) +{ + s << "if (_i < 0 || _i >= (Py_ssize_t) " << CPP_SELF_VAR << "->size()) {\n" + << indent << "PyErr_SetString(PyExc_IndexError, \"" << errorMsg << "\");\n" + << errorReturn << outdent << "}\n"; +} + +QString CppGenerator::writeReprFunctionHeader(TextStream &s, const GeneratorContext &context) +{ + QString funcName = cpythonBaseName(context.metaClass()) + REPR_FUNCTION; + s << "extern \"C\"\n{\n" + << "static PyObject *" << funcName << "(PyObject *self)\n{\n" << indent; + return funcName; +} + +QString CppGenerator::writeReprFunction(TextStream &s, + const GeneratorContext &context, + uint indirections) +{ + const auto metaClass = context.metaClass(); + QString funcName = writeReprFunctionHeader(s, context); + writeCppSelfDefinition(s, context); + s << R"(QBuffer buffer; +buffer.open(QBuffer::ReadWrite); +QDebug dbg(&buffer); +dbg << )"; + if (metaClass->typeEntry()->isValue() || indirections == 0) + s << '*'; + s << CPP_SELF_VAR << R"(; +buffer.close(); +QByteArray str = buffer.data(); +const auto idx = str.indexOf('('); +auto *typeName = Py_TYPE(self)->tp_name; +if (idx >= 0) +)" << indent << "str.replace(0, idx, typeName);\n" << outdent + << "str = str.trimmed();\n" + << "Shiboken::AutoDecRef tpDict(PepType_GetDict(Py_TYPE(self)));\n" + << "PyObject *mod = PyDict_GetItem(tpDict.object(), Shiboken::PyMagicName::module());\n"; + // PYSIDE-595: The introduction of heap types has the side effect that the module name + // is always prepended to the type name. Therefore the strchr check: + s << "if (mod != nullptr && std::strchr(typeName, '.') == nullptr)\n" << indent + << "return Shiboken::String::fromFormat(\"<%s.%s at %p>\"," + " Shiboken::String::toCString(mod), str.constData(), self);\n" + << outdent + << "return Shiboken::String::fromFormat(\"<%s at %p>\", str.constData(), self);\n"; + writeReprFunctionFooter(s); + return funcName; +} + +void CppGenerator::writeReprFunctionFooter(TextStream &s) +{ + s << outdent << "}\n} // extern C\n\n"; +} diff --git a/sources/shiboken6/generator/shiboken/cppgenerator.h b/sources/shiboken6/generator/shiboken/cppgenerator.h new file mode 100644 index 000000000..a31c2ca14 --- /dev/null +++ b/sources/shiboken6/generator/shiboken/cppgenerator.h @@ -0,0 +1,567 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef CPPGENERATOR_H +#define CPPGENERATOR_H + +#include "shibokengenerator.h" +#include "include.h" +#include "modifications_typedefs.h" + +#include <QtCore/QFlags> +#include <QtCore/QSet> +#include <QtCore/QHash> + +#include <memory> +#include <utility> + +class OverloadDataNode; +class OverloadDataRootNode; +struct PyMethodDefEntry; + +/** + * The CppGenerator generate the implementations of C++ bindings classes. + */ +class CppGenerator : public ShibokenGenerator +{ +public: + enum class ErrorReturn { + Default, // "{}" + Zero, + MinusOne, + Void + }; + + enum CppSelfDefinitionFlag { + HasStaticOverload = 0x1, + HasClassMethodOverload = 0x2, + CppSelfAsReference = 0x4 + }; + Q_DECLARE_FLAGS(CppSelfDefinitionFlags, CppSelfDefinitionFlag) + + CppGenerator(); + + const char *name() const override { return "Source generator"; } + +protected: + QString fileNameForContext(const GeneratorContext &context) const override; + void generateClass(TextStream &s, const GeneratorContext &classContext) override; + bool finishGeneration() override; + +private: + struct VirtualMethodReturn + { + QString statement; + bool needsReference = false; + }; + + + void generateSmartPointerClass(TextStream &s, const GeneratorContext &classContext); + void generateIncludes(TextStream &s, const GeneratorContext &classContext, + const IncludeGroupList &includes = {}, + const AbstractMetaClassCList &innerClasses = {}) const; + static void writeInitFunc(TextStream &declStr, TextStream &callStr, + const QString &initFunctionName, + const TypeEntryCPtr &enclosingEntry, + const QString &pythonName, bool lazy = true); + static void writeCacheResetNative(TextStream &s, const GeneratorContext &classContext); + void writeConstructorNative(TextStream &s, const GeneratorContext &classContext, + const AbstractMetaFunctionCPtr &func) const; + static void writeDestructorNative(TextStream &s, const GeneratorContext &classContext); + + QString getVirtualFunctionReturnTypeName(const AbstractMetaFunctionCPtr &func) const; + static std::pair<QString, QChar> virtualMethodNativeArg(const AbstractMetaFunctionCPtr &func, + const AbstractMetaArgument &arg); + static void writeVirtualMethodNativeVectorCallArgs(TextStream &s, + const AbstractMetaFunctionCPtr &func, + const AbstractMetaArgumentList &arguments, + const QList<int> &invalidateArgs); + static void writeVirtualMethodNativeArgs(TextStream &s, + const AbstractMetaFunctionCPtr &func, + const AbstractMetaArgumentList &arguments, + const QList<int> &invalidateArgs); + void writeVirtualMethodNative(TextStream &s, + const AbstractMetaFunctionCPtr &func, + int cacheIndex) const; + void writeVirtualMethodPythonOverride(TextStream &s, + const AbstractMetaFunctionCPtr &func, + const CodeSnipList &snips, + const VirtualMethodReturn &returnStatement) const; + void writeUserAddedPythonOverride(TextStream &s, + const AbstractMetaFunctionCPtr &func) const; + void writeVirtualMethodCppCall(TextStream &s, const AbstractMetaFunctionCPtr &func, + const QString &funcName, const QList<CodeSnip> &snips, + const AbstractMetaArgument *lastArg, const TypeEntryCPtr &retType, + const QString &returnStatement, bool hasGil) const; + + static VirtualMethodReturn virtualMethodReturn(const ApiExtractorResult &api, + const AbstractMetaFunctionCPtr &func, + const FunctionModificationList &functionModifications); + void writeMetaObjectMethod(TextStream &s, const GeneratorContext &classContext) const; + static void writeMetaCast(TextStream &s, const GeneratorContext &classContext); + + void writeEnumConverterFunctions(TextStream &s, const AbstractMetaEnum &metaEnum) const; + void writeConverterFunctions(TextStream &s, const AbstractMetaClassCPtr &metaClass, + const GeneratorContext &classContext) const; + void writeCustomConverterFunctions(TextStream &s, + const CustomConversionPtr &customConversion) const; + void writeConverterRegister(TextStream &s, const AbstractMetaClassCPtr &metaClass, + const GeneratorContext &classContext) const; + static void writeCustomConverterRegister(TextStream &s, + const CustomConversionPtr &customConversion, + const QString &converterVar); + + void writeContainerConverterFunctions(TextStream &s, + const AbstractMetaType &containerType) const; + + struct OpaqueContainerData + { + QString name; + QString checkFunctionName; + QString converterCheckFunctionName; + QString pythonToConverterFunctionName; + QString registrationCode; + }; + + OpaqueContainerData + writeOpaqueContainerConverterFunctions(TextStream &s, + const AbstractMetaType &containerType, + QSet<AbstractMetaType> *valueTypes) const; + void writeOpaqueContainerValueConverter(TextStream &s, + const AbstractMetaType &valueType) const; + + void writeSmartPointerConverterFunctions(TextStream &s, + const AbstractMetaType &smartPointerType) const; + + static bool needsArgumentErrorHandling(const OverloadData &overloadData); + static void writeMethodWrapperPreamble(TextStream &s, + const OverloadData &overloadData, + const GeneratorContext &context, + ErrorReturn errorReturn = ErrorReturn::Default); + void writeConstructorWrapper(TextStream &s, + const OverloadData &overloadData, + const GeneratorContext &classContext) const; + void writeMethodWrapper(TextStream &s, const OverloadData &overloadData, + const GeneratorContext &classContext) const; + void writeMethodWrapper(TextStream &s, TextStream &definitionStream, + TextStream &signatureStream, + const AbstractMetaFunctionCList &overloads, + const GeneratorContext &classContext) const; + static void writeArgumentsInitializer(TextStream &s, const OverloadData &overloadData, + ErrorReturn errorReturn = ErrorReturn::Default); + static void writeCppSelfConversion(TextStream &s, + const GeneratorContext &context, + const QString &className, + bool useWrapperClass); + static void writeSmartPointerCppSelfConversion(TextStream &s, + const GeneratorContext &context); + + static void writeCppSelfVarDef(TextStream &s, CppSelfDefinitionFlags flags = {}); + static void writeSmartPointerCppSelfDefinition(TextStream &s, + const GeneratorContext &, + ErrorReturn errorReturn = ErrorReturn::Default, + CppSelfDefinitionFlags flags = {}); + static void writeCppSelfDefinition(TextStream &s, + const AbstractMetaFunctionCPtr &func, + const GeneratorContext &context, + ErrorReturn errorReturn = ErrorReturn::Default, + CppSelfDefinitionFlags flags = {}); + static void writeCppSelfDefinition(TextStream &s, + const GeneratorContext &context, + ErrorReturn errorReturn = ErrorReturn::Default, + CppSelfDefinitionFlags flags = {}); + + static void writeErrorSection(TextStream &s, + const OverloadData &overloadData, + ErrorReturn errorReturn); + + static QString returnErrorWrongArguments(const OverloadData &overloadData, + ErrorReturn errorReturn); + + static void writeFunctionReturnErrorCheckSection(TextStream &s, + ErrorReturn errorReturn, + bool hasReturnValue = true); + + /// Writes the check section for the validity of wrapped C++ objects. + static void writeInvalidPyObjectCheck(TextStream &s, const QString &pyObj, + ErrorReturn errorReturn); + + static void writeTypeCheck(TextStream &s, const AbstractMetaType &argType, + const QString &argumentName, + bool isNumber = false, bool rejectNull = false); + static void writeTypeCheck(TextStream &s, const QString &customType, + const QString &argumentName); + static void writeTypeCheck(TextStream& s, const std::shared_ptr<OverloadDataNode> &overloadData, + const QString &argumentName); + + static void replacePolymorphicIdPlaceHolders(const AbstractMetaClassCPtr &metaClass, + QString *id); + static void writeTypeDiscoveryFunction(TextStream &s, + const AbstractMetaClassCPtr &metaClass); + + static void writeSetattroDefinition(TextStream &s, const AbstractMetaClassCPtr &metaClass); + static void writeSetattroDefaultReturn(TextStream &s); + static void writeSmartPointerSetattroFunction(TextStream &s, + const GeneratorContext &context); + void writeSetattroFunction(TextStream &s, + AttroCheck attroCheck, + const GeneratorContext &context) const; + static void writeGetattroDefinition(TextStream &s, const AbstractMetaClassCPtr &metaClass); + static void writeSmartPointerGetattroFunction(TextStream &s, + const GeneratorContext &context, + const BoolCastFunctionOptional &boolCast); + void writeGetattroFunction(TextStream &s, AttroCheck attroCheck, + const GeneratorContext &context) const; + QString qObjectGetAttroFunction() const; + + static void writeNbBoolFunction(const GeneratorContext &context, + const BoolCastFunction &f, + TextStream &s); + static void writeNbBoolExpression(TextStream &s, const BoolCastFunction &f, bool invert = false); + + /** + * Writes Python to C++ conversions for arguments on Python wrappers. + * If implicit conversions, and thus new object allocation, are needed, + * code to deallocate a possible new instance is also generated. + * \param s text stream to write + * \param argType a pointer to the argument type to be converted + * \param argName C++ argument name + * \param pyArgName Python argument name + * \param context the current meta class + * \param defaultValue an optional default value to be used instead of the conversion result + * \param castArgumentAsUnused if true the converted argument is cast as unused to avoid compiler warnings + */ + qsizetype writeArgumentConversion(TextStream &s, const AbstractMetaType &argType, + const QString &argName, const QString &pyArgName, + ErrorReturn errorReturn, + const AbstractMetaClassCPtr &context = {}, + const QString &defaultValue = QString(), + bool castArgumentAsUnused = false) const; + + /** + * Returns the AbstractMetaType for a function argument. + * If the argument type was modified in the type system, this method will + * try to build a new type based on the type name defined in the type system. + * \param func The function which owns the argument. + * \param index Argument index in the function signature. + * \return The type of the argument indicated by \p index. + */ + static AbstractMetaType + getArgumentType(const AbstractMetaFunctionCPtr &func, int index); + + /// Writes the Python to C++ Conversion for function arguments and return + /// values of virtual methods for wrappers. + /// \return The number of indirections in case of return types + qsizetype writePythonToCppTypeConversion(TextStream &s, + const AbstractMetaType &type, + const QString &pyIn, + const QString &cppOut, + const AbstractMetaClassCPtr &context = {}, + const QString &defaultValue = {}) const; + + /// Writes the conversion rule for arguments of regular and virtual methods. + void writeConversionRule(TextStream &s, const AbstractMetaFunctionCPtr &func, + TypeSystem::Language language, bool usesPyArgs) const; + /// Writes the conversion rule for the return value of a method. + void writeConversionRule(TextStream &s, const AbstractMetaFunctionCPtr &func, + TypeSystem::Language language, const QString &outputVar) const; + + /** + * Set the Python method wrapper return value variable to Py_None if + * there are return types different from void in any of the other overloads + * for the function passed as parameter. + * \param s text stream to write + * \param func a pointer to the function that will possibly return Py_None + * \param thereIsReturnValue indicates if the return type of any of the other overloads + * for this function is different from 'void' + */ + static void writeNoneReturn(TextStream &s, const AbstractMetaFunctionCPtr &func, + bool thereIsReturnValue); + + /** + * Writes the Python function wrapper overload decisor that selects which C++ + * method/function to call with the received Python arguments. + * \param s text stream to write + * \param overloadData the overload data describing all the possible overloads for the function/method + */ + void writeOverloadedFunctionDecisor(TextStream &s, const OverloadData &overloadData, + ErrorReturn errorReturn) const; + /// Recursive auxiliar method to the other writeOverloadedFunctionDecisor. + void writeOverloadedFunctionDecisorEngine(TextStream &s, + const OverloadData &overloadData, + const OverloadDataRootNode *node) const; + + /// Writes calls to all the possible method/function overloads. + void writeFunctionCalls(TextStream &s, + const OverloadData &overloadData, + const GeneratorContext &context, + ErrorReturn errorReturn) const; + + /// Writes the call to a single function usually from a collection of overloads. + void writeSingleFunctionCall(TextStream &s, + const OverloadData &overloadData, + const AbstractMetaFunctionCPtr &func, + const GeneratorContext &context, + ErrorReturn errorReturn) const; + + /// Returns the name of a C++ to Python conversion function. + static QString cppToPythonFunctionName(const QString &sourceTypeName, QString targetTypeName = QString()); + + /// Returns the name of a Python to C++ conversion function. + static QString pythonToCppFunctionName(const QString &sourceTypeName, const QString &targetTypeName); + static QString pythonToCppFunctionName(const AbstractMetaType &sourceType, const AbstractMetaType &targetType); + static QString pythonToCppFunctionName(const TargetToNativeConversion &toNative, + const TypeEntryCPtr &targetType); + + /// Returns the name of a Python to C++ convertible check function. + static QString convertibleToCppFunctionName(const QString &sourceTypeName, const QString &targetTypeName); + static QString convertibleToCppFunctionName(const AbstractMetaType &sourceType, const AbstractMetaType &targetType); + static QString convertibleToCppFunctionName(const TargetToNativeConversion &toNative, + const TypeEntryCPtr &targetType); + + /// Writes a C++ to Python conversion function. + void writeCppToPythonFunction(TextStream &s, const QString &code, const QString &sourceTypeName, + QString targetTypeName = QString()) const; + void writeCppToPythonFunction(TextStream &s, const CustomConversionPtr &customConversion) const; + void writeCppToPythonFunction(TextStream &s, const AbstractMetaType &containerType) const; + /// Main target type name of a container (for naming the functions). + static QString containerNativeToTargetTypeName(const ContainerTypeEntryCPtr &type); + + /// Writes a Python to C++ conversion function. + void writePythonToCppFunction(TextStream &s, const QString &code, const QString &sourceTypeName, + const QString &targetTypeName) const; + + /// Writes a Python to C++ convertible check function. + static void writeIsPythonConvertibleToCppFunction(TextStream &s, + const QString &sourceTypeName, + const QString &targetTypeName, + const QString &condition, + QString pythonToCppFuncName = QString(), + bool acceptNoneAsCppNull = false); + + /// Writes a pair of Python to C++ conversion and check functions. + void writePythonToCppConversionFunctions(TextStream &s, + const AbstractMetaType &sourceType, + const AbstractMetaType &targetType, + QString typeCheck = QString(), + QString conversion = QString(), + const QString &preConversion = QString()) const; + /// Writes a pair of Python to C++ conversion and check functions for implicit conversions. + void writePythonToCppConversionFunctions(TextStream &s, + const TargetToNativeConversion &toNative, + const TypeEntryCPtr &targetType) const; + + /// Writes a pair of Python to C++ conversion and check functions for instantiated container types. + void writePythonToCppConversionFunctions(TextStream &s, + const AbstractMetaType &containerType) const; + + void writePythonToCppConversionFunction(TextStream &s, + const AbstractMetaType &containerType, + const TargetToNativeConversion &conv) const; + + static void writeAddPythonToCppConversion(TextStream &s, const QString &converterVar, + const QString &pythonToCppFunc, + const QString &isConvertibleFunc); + + static void writeSetPythonToCppPointerConversion(TextStream &s, const QString &converterVar, + const QString &pythonToCppFunc, + const QString &isConvertibleFunc); + + static void writeNamedArgumentResolution(TextStream &s, + const AbstractMetaFunctionCPtr &func, + bool usePyArgs, + const OverloadData &overloadData, + ErrorReturn errorReturn); + + /// Returns a string containing the name of an argument for the given function and argument index. + static QString argumentNameFromIndex(const ApiExtractorResult &api, + const AbstractMetaFunctionCPtr &func, int argIndex); + /// Returns the class for an ownership modification of the argument. + /// Throws if the argument is not a class or cannot be found. + static AbstractMetaClassCPtr + argumentClassFromIndex(const ApiExtractorResult &api, + const AbstractMetaFunctionCPtr &func, int argIndex); + + void writeMethodCall(TextStream &s, const AbstractMetaFunctionCPtr &func, + const GeneratorContext &context, bool usesPyArgs, + int maxArgs, const QList<qsizetype> &argumentIndirections, + ErrorReturn errorReturn) const; + + static QString getInitFunctionName(const GeneratorContext &context) ; + static QString getSimpleClassInitFunctionName(const AbstractMetaClassCPtr &metaClass); + static QString + getSimpleClassStaticFieldsInitFunctionName(const AbstractMetaClassCPtr &metaClass); + + static void writeSignatureStrings(TextStream &s, const QString &signatures, + const QString &arrayName, + const char *comment); + void writeInitInheritance(TextStream &s) const; + void writeClassRegister(TextStream &s, + const AbstractMetaClassCPtr &metaClass, + const GeneratorContext &classContext, + const QString &signatures) const; + static QStringList pyBaseTypes(const AbstractMetaClassCPtr &metaClass); + static QString destructorClassName(const AbstractMetaClassCPtr &metaClass, + const GeneratorContext &classContext); + static void writeStaticFieldInitialization(TextStream &s, + const AbstractMetaClassCPtr &metaClass); + void writeClassDefinition(TextStream &s, + const AbstractMetaClassCPtr &metaClass, + const GeneratorContext &classContext); + QByteArrayList methodDefinitionParameters(const OverloadData &overloadData) const; + QList<PyMethodDefEntry> methodDefinitionEntries(const OverloadData &overloadData) const; + + void writeSignatureInfo(TextStream &s, const OverloadData &overloads) const; + QString signatureParameter(const AbstractMetaArgument &arg) const; + /// Writes the implementation of all methods part of python sequence protocol + void writeSequenceMethods(TextStream &s, + const AbstractMetaClassCPtr &metaClass, + const GeneratorContext &context) const; + static void writeTypeAsSequenceDefinition(TextStream &s, + const AbstractMetaClassCPtr &metaClass); + + /// Writes the PyMappingMethods structure for types that supports the python mapping protocol. + static void writeTypeAsMappingDefinition(TextStream &s, + const AbstractMetaClassCPtr &metaClass); + void writeMappingMethods(TextStream &s, + const AbstractMetaClassCPtr &metaClass, + const GeneratorContext &context) const; + + void writeTypeAsNumberDefinition(TextStream &s, const AbstractMetaClassCPtr &metaClass) const; + + static void writeTpTraverseFunction(TextStream &s, const AbstractMetaClassCPtr &metaClass); + static void writeTpClearFunction(TextStream &s, const AbstractMetaClassCPtr &metaClass); + + static QString writeCopyFunction(TextStream &s, TextStream &definitionStream, + TextStream &signatureStream, const GeneratorContext &context); + + static QString cppFieldAccess(const AbstractMetaField &metaField, + const GeneratorContext &context); + static void writeGetterFunction(TextStream &s, + const AbstractMetaField &metaField, + const GeneratorContext &context); + static void writeGetterFunction(TextStream &s, + const QPropertySpec &property, + const GeneratorContext &context); + static void writeSetterFunctionPreamble(TextStream &s, + const QString &name, + const QString &funcName, + const AbstractMetaType &type, + const GeneratorContext &context); + static void writeSetterFunction(TextStream &s, + const AbstractMetaField &metaField, + const GeneratorContext &context); + static void writeSetterFunction(TextStream &s, + const QPropertySpec &property, + const GeneratorContext &context); + + static void writeRichCompareFunctionHeader(TextStream &s, + const QString &baseName, + const GeneratorContext &context); + void writeRichCompareFunction(TextStream &s, const GeneratorContext &context) const; + void writeSmartPointerRichCompareFunction(TextStream &s, const GeneratorContext &context) const; + + static void writeEnumsInitialization(TextStream &s, AbstractMetaEnumList &enums); + static bool writeEnumInitialization(TextStream &s, const AbstractMetaEnum &metaEnum); + + static void writeSignalInitialization(TextStream &s, const AbstractMetaClassCPtr &metaClass); + + /// Writes the function that registers the multiple inheritance information + /// for the classes that need it. + static void writeMultipleInheritanceInitializerFunction(TextStream &s, + const AbstractMetaClassCPtr &metaClass); + /// Writes the implementation of special cast functions, used when we need + /// to cast a class with multiple inheritance. + static void writeSpecialCastFunction(TextStream &s, const AbstractMetaClassCPtr &metaClass); + + static void writePrimitiveConverterInitialization(TextStream &s, + const CustomConversionPtr &customConversion); + static void writeEnumConverterInitialization(TextStream &s, const AbstractMetaEnum &metaEnum); + static QString writeContainerConverterInitialization(TextStream &s, + const AbstractMetaType &type, + const ApiExtractorResult &api); + void writeSmartPointerConverterInitialization(TextStream &s, const AbstractMetaType &ype) const; + + static QString typeInitStruct(const TypeEntryCPtr &te); + static void writeExtendedConverterInitialization(TextStream &s, + const TypeEntryCPtr &externalType, + const AbstractMetaClassCList &conversions); + + void writeParentChildManagement(TextStream &s, const AbstractMetaFunctionCPtr &func, + bool usesPyArgs, + bool userHeuristicForReturn) const; + bool writeParentChildManagement(TextStream &s, const AbstractMetaFunctionCPtr &func, + int argIndex, + bool usePyArgs, + bool userHeuristicPolicy) const; + void writeReturnValueHeuristics(TextStream &s, const AbstractMetaFunctionCPtr &func) const; + static void writeInitQtMetaTypeFunctionBody(TextStream &s, const GeneratorContext &context); + + /** + * Returns the multiple inheritance initializer function for the given class. + * \param metaClass the class for whom the function name must be generated. + * \return name of the multiple inheritance information initializer function or + * an empty string if there is no multiple inheritance in its ancestry. + */ + static QString multipleInheritanceInitializerFunctionName(const AbstractMetaClassCPtr &metaClass); + + /// Returns a list of all classes to which the given class could be cast. + static QStringList getAncestorMultipleInheritance(const AbstractMetaClassCPtr &metaClass); + + /// Returns true if the given class supports the python number protocol + static bool supportsNumberProtocol(const AbstractMetaClassCPtr &metaClass); + + /// Returns true if the given class supports the python sequence protocol + static bool supportsSequenceProtocol(const AbstractMetaClassCPtr &metaClass) ; + + /// Returns true if the given class supports the python mapping protocol + static bool supportsMappingProtocol(const AbstractMetaClassCPtr &metaClass) ; + + /// Returns true if generator should produce getters and setters for the given class. + static bool shouldGenerateGetSetList(const AbstractMetaClassCPtr &metaClass); + + static bool hasHashFunction(const AbstractMetaClassCPtr &c); + static void writeHashFunction(TextStream &s, const GeneratorContext &context); + + /// Write default implementations for sequence protocol + void writeDefaultSequenceMethods(TextStream &s, const GeneratorContext &context) const; + /// Helper function for writeStdListWrapperMethods. + static void writeIndexError(TextStream &s, const QString &errorMsg, + ErrorReturn errorReturn); + + static QString writeReprFunctionHeader(TextStream &s, const GeneratorContext &context); + static QString writeReprFunction(TextStream &s, + const GeneratorContext &context, + uint indirections); + static QString writeSmartPointerReprFunction(TextStream &s, + const GeneratorContext &context); + static QString writeSmartPointerDirFunction(TextStream &s, + TextStream &definitionStream, + TextStream &signatureStream, + const GeneratorContext &context); + static void writeReprFunctionFooter(TextStream &s); + static void writePyMethodDefs(TextStream &s, const QString &className, + const QString &methodsDefinitions); + + void writeModuleCodeSnips(TextStream &s, const CodeSnipList &codeSnips, + TypeSystem::CodeSnipPosition position, + TypeSystem::Language language) const; + + static bool hasBoolCast(const AbstractMetaClassCPtr &metaClass) + { return boolCast(metaClass).has_value(); } + + std::optional<AbstractMetaType> + findSmartPointerInstantiation(const SmartPointerTypeEntryCPtr &pointer, + const TypeEntryCPtr &pointee) const; + void clearTpFuncs(); + static QString chopType(QString s); + + QHash<QString, QString> m_tpFuncs; + QHash<QString, QString> m_nbFuncs; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(CppGenerator::CppSelfDefinitionFlags) + +TextStream &operator<<(TextStream &s, CppGenerator::ErrorReturn r); + +#endif // CPPGENERATOR_H diff --git a/sources/shiboken6/generator/shiboken/cppgenerator_container.cpp b/sources/shiboken6/generator/shiboken/cppgenerator_container.cpp new file mode 100644 index 000000000..00e0cabea --- /dev/null +++ b/sources/shiboken6/generator/shiboken/cppgenerator_container.cpp @@ -0,0 +1,272 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "cppgenerator.h" +#include "generatorstrings.h" +#include <abstractmetalang.h> +#include "apiextractorresult.h" +#include "ctypenames.h" +#include "containertypeentry.h" +#include "textstream.h" +#include "typedatabase.h" + +#include <QtCore/QDebug> + +#include <algorithm> + +using namespace Qt::StringLiterals; + +// Write a PyMethodDef entry, allowing for registering C++ functions +// under different names for Python. +static void writeMethod(TextStream &s, const QString &privateObjType, + const char *cppName, const char *pythonName, + const char *flags) +{ + if (pythonName == nullptr) + pythonName = cppName; + s << "{\"" << pythonName << "\", reinterpret_cast<PyCFunction>(" + << privateObjType << "::" << cppName << "), "<< flags + << ", \"" << /* doc */ pythonName << "\"},\n"; +} + +static inline void writeMethod(TextStream &s, const QString &privateObjType, + const char *cppName, const char *pythonName = nullptr) +{ + writeMethod(s, privateObjType, cppName, pythonName, "METH_O"); +} + +static inline void writeNoArgsMethod(TextStream &s, const QString &privateObjType, + const char *cppName, const char *pythonName = nullptr) +{ + writeMethod(s, privateObjType, cppName, pythonName, "METH_NOARGS"); +} + +static void writeSlot(TextStream &s, const char *tpName, const char *value) +{ + s << '{' << tpName << ", reinterpret_cast<void *>(" << value << ")},\n"; +} + +static void writeSlot(TextStream &s, const QString &privateObjType, + const char *tpName, const char *methodName) +{ + s << '{' << tpName << ", reinterpret_cast<void *>(" << privateObjType + << "::" << methodName << ")},\n"; +} + +// Write creation function from C++ reference, used by field accessors +// and getters which are within extern "C" + +enum ContainerCreationFlag +{ + None = 0, + Const = 0x1, + Allocate = 0x2 +}; + +Q_DECLARE_FLAGS(ContainerCreationFlags, ContainerCreationFlag) +Q_DECLARE_OPERATORS_FOR_FLAGS(ContainerCreationFlags) + +static void writeContainerCreationFunc(TextStream &s, + const QString &funcName, + const QString &typeFName, + const QString &containerSignature, + ContainerCreationFlags flags = {}) +{ + + // creation function from C++ reference, used by field accessors + // which are within extern "C" + s << "extern \"C\" PyObject *" << funcName << '('; + if (flags.testFlag(ContainerCreationFlag::Const)) + s << "const "; + s << containerSignature << "* ct)\n{\n" << indent + << "auto *container = PyObject_New(ShibokenContainer, " << typeFName << "());\n" + << "auto *d = new ShibokenSequenceContainerPrivate<" + << containerSignature << ">();\n"; + if (flags.testFlag(ContainerCreationFlag::Allocate)) { + s << "d->m_list = new " << containerSignature << "(*ct);\n" + << "d->m_ownsList = true;\n"; + } else if (flags.testFlag(ContainerCreationFlag::Const)) { + s << "d->m_list = const_cast<" << containerSignature << " *>(ct);\n" + << "d->m_const = true;\n"; + } else { + s << "d->m_list = ct;\n"; + } + s << "container->d = d;\n"; + s << "return reinterpret_cast<PyObject *>(container);\n" << outdent + << "}\n\n"; +} + +// Generate template specialization of value converter helper +void CppGenerator::writeOpaqueContainerValueConverter(TextStream &s, + const AbstractMetaType &valueType) const +{ + // Generate template specialization of value converter helper unless it is already there + const QString valueTypeName = valueType.cppSignature(); + const QString checkFunction = cpythonCheckFunction(valueType); + + s << "template <>\nstruct ShibokenContainerValueConverter<" + << valueTypeName << ">\n{\n"; + // Type check + s << indent << "static bool checkValue(PyObject *" << PYTHON_ARG << ")\n{\n" + << indent << "return " << checkFunction; + if (!checkFunction.contains(u'(')) + s << '('; + s << PYTHON_ARG << ");\n" + << outdent << "}\n\n"; + + // C++ to Python + const bool passByConstRef = valueType.indirectionsV().isEmpty() + && !valueType.isCppPrimitive(); + s << "static PyObject *convertValueToPython("; + if (passByConstRef) + s << "const "; + s << valueTypeName << ' '; + if (passByConstRef) + s << '&'; + s << CPP_ARG << ")\n{\n" << indent << "return "; + writeToPythonConversion(s, valueType, nullptr, CPP_ARG); + s << ";\n" << outdent << "}\n\n"; + + // Python to C++ + s << "static std::optional<" << valueTypeName << "> convertValueToCpp(PyObject *" + << PYTHON_ARG << ")\n{\n" << indent; + s << PYTHON_TO_CPPCONVERSION_STRUCT << ' ' << PYTHON_TO_CPP_VAR << ";\n" + << "if (!("; + writeTypeCheck(s, valueType, PYTHON_ARG), isNumber(valueType.typeEntry()); + s << ")) {\n" << indent + << "Shiboken::Errors::setWrongContainerType();\n" + << "return {};\n" << outdent << "}\n"; + writePythonToCppTypeConversion(s, valueType, PYTHON_ARG, CPP_ARG, nullptr, {}); + s << "return " << CPP_ARG << ";\n" << outdent << "}\n" << outdent << "};\n\n"; +} + +// Generate code for a type wrapping a C++ container instantiation +CppGenerator::OpaqueContainerData + CppGenerator::writeOpaqueContainerConverterFunctions(TextStream &s, + const AbstractMetaType &containerType, + QSet<AbstractMetaType> *valueTypes) const +{ + OpaqueContainerData result; + const auto &valueType = containerType.instantiations().constFirst(); + const auto containerTypeEntry = std::static_pointer_cast<const ContainerTypeEntry>(containerType.typeEntry()); + result.name = + containerTypeEntry->opaqueContainerName(containerType.instantiationCppSignatures()); + + const auto cppSignature = containerType.cppSignature(); + s << "\n// Binding for " << cppSignature << "\n\n"; + + if (!valueTypes->contains(valueType)) { + valueTypes->insert(valueType); + writeOpaqueContainerValueConverter(s, valueType); + } + + const QString privateObjType = u"ShibokenSequenceContainerPrivate<"_s + + cppSignature + u'>'; + + // methods + const QString &containerName = containerType.name(); + const bool isStdVector = containerName == u"std::vector"; + const auto kind = containerTypeEntry->containerKind(); + const bool isFixed = kind == ContainerTypeEntry::SpanContainer || containerName == u"std::array"; + const QString methods = result.name + u"_methods"_s; + s << "static PyMethodDef " << methods << "[] = {\n" << indent; + if (!isFixed) { + writeMethod(s, privateObjType, "push_back"); + writeMethod(s, privateObjType, "push_back", "append"); // Qt convention + writeNoArgsMethod(s, privateObjType, "clear"); + writeNoArgsMethod(s, privateObjType, "pop_back"); + writeNoArgsMethod(s, privateObjType, "pop_back", "removeLast"); // Qt convention + if (!isStdVector) { + writeMethod(s, privateObjType, "push_front"); + writeMethod(s, privateObjType, "push_front", "prepend"); // Qt convention + writeNoArgsMethod(s, privateObjType, "pop_front"); + writeMethod(s, privateObjType, "pop_front", "removeFirst"); // Qt convention + } + writeMethod(s, privateObjType, "reserve"); // SFINAE'd out for list + writeNoArgsMethod(s, privateObjType, "capacity"); + } + writeNoArgsMethod(s, privateObjType, "data"); + writeNoArgsMethod(s, privateObjType, "constData"); + s << "{nullptr, nullptr, 0, nullptr} // Sentinel\n" + << outdent << "};\n\n"; + + // slots + const QString slotsList = result.name + u"_slots"_s; + s << "static PyType_Slot " << slotsList << "[] = {\n" << indent; + writeSlot(s, privateObjType, "Py_tp_init", "tpInit"); + const auto *tpNew = containerTypeEntry->viewOn() == nullptr ? "tpNew" : "tpNewInvalid"; + writeSlot(s, privateObjType, "Py_tp_new", tpNew); + writeSlot(s, privateObjType, "Py_tp_free", "tpFree"); + writeSlot(s, "Py_tp_dealloc", "Sbk_object_dealloc"); // FIXME? + writeSlot(s, "Py_tp_methods", methods.toUtf8().constData()); + writeSlot(s, privateObjType, "Py_sq_ass_item", "sqSetItem"); + writeSlot(s, privateObjType, "Py_sq_length", "sqLen"); + writeSlot(s, privateObjType, "Py_sq_item", "sqGetItem"); + s << "{0, nullptr}\n" << outdent << "};\n\n"; + + // spec + const QString specName = result.name + u"_spec"_s; + const QString name = TypeDatabase::instance()->defaultPackageName() + + u'.' + result.name; + s << "static PyType_Spec " << specName << " = {\n" << indent + << "\"" << name.count(u'.') << ':' << name << "\",\n" + << "sizeof(ShibokenContainer),\n0,\nPy_TPFLAGS_DEFAULT,\n" + << slotsList << outdent << "\n};\n\n"; + + // type creation function that sets a key in the type dict. + const QString typeCreationFName = u"create"_s + result.name + u"Type"_s; + s << "static inline PyTypeObject *" << typeCreationFName << "()\n{\n" << indent + << "auto *result = reinterpret_cast<PyTypeObject *>(SbkType_FromSpec(&" + << specName << "));\nPy_INCREF(Py_True);\n" + << "Shiboken::AutoDecRef tpDict(PepType_GetDict(result));\n" + << "PyDict_SetItem(tpDict.object(), " + "Shiboken::PyMagicName::opaque_container(), Py_True);\n" + << "return result;\n" << outdent << "}\n\n"; + + // _TypeF() function + const QString typeFName = result.name + u"_TypeF"_s; + s << "static PyTypeObject *" << typeFName << "()\n{\n" << indent + << "static PyTypeObject *type = " << typeCreationFName + << "();\nreturn type;\n" << outdent << "}\n\n"; + + // creation functions from C++ references + ContainerCreationFlags flags; + if (kind == ContainerTypeEntry::SpanContainer) + flags.setFlag(ContainerCreationFlag::Allocate); + + writeContainerCreationFunc(s, u"create"_s + result.name, typeFName, + containerType.cppSignature(), flags); + flags.setFlag(ContainerCreationFlag::Const); + writeContainerCreationFunc(s, u"createConst"_s + result.name, typeFName, + containerType.cppSignature(), flags); + + // Check function + result.checkFunctionName = result.name + u"_Check"_s; + s << "extern \"C\" int " << result.checkFunctionName << "(PyObject *" << PYTHON_ARG + << ")\n{\n" << indent << "return " << PYTHON_ARG << " != nullptr && " + << PYTHON_ARG << " != Py_None && " << PYTHON_ARG << "->ob_type == " + << typeFName << "();\n" << outdent << "}\n\n"; + + // SBK converter Python to C++ + result.pythonToConverterFunctionName = u"PythonToCpp"_s + result.name; + s << "extern \"C\" void " << result.pythonToConverterFunctionName + << "(PyObject *" << PYTHON_ARG << ", void *cppOut)\n{\n" << indent + << "auto *d = ShibokenSequenceContainerPrivate<" << cppSignature + << ">::get(" << PYTHON_ARG << ");\n" + << "*reinterpret_cast<" << cppSignature << "**>(cppOut) = d->m_list;\n" + << outdent << "}\n\n"; + + // SBK check function for converting Python to C++ that returns the converter + result.converterCheckFunctionName = u"is"_s + result.name + u"PythonToCppConvertible"_s; + s << "extern \"C\" PythonToCppFunc " << result.converterCheckFunctionName + << "(PyObject *" << PYTHON_ARG << ")\n{\n" << indent << "if (" + << result.checkFunctionName << '(' << PYTHON_ARG << "))\n" << indent + << "return " << result.pythonToConverterFunctionName << ";\n" + << outdent << "return {};\n" << outdent << "}\n\n"; + + QTextStream(&result.registrationCode) << "ob_type = reinterpret_cast<PyObject *>(" + << typeFName + << "());\nPy_XINCREF(ob_type);\nPyModule_AddObject(module, \"" + << result.name << "\", ob_type);\n"; + return result; +} diff --git a/sources/shiboken6/generator/shiboken/cppgenerator_smartpointer.cpp b/sources/shiboken6/generator/shiboken/cppgenerator_smartpointer.cpp new file mode 100644 index 000000000..1b893640a --- /dev/null +++ b/sources/shiboken6/generator/shiboken/cppgenerator_smartpointer.cpp @@ -0,0 +1,486 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "cppgenerator.h" +#include "generatorstrings.h" +#include "generatorcontext.h" +#include <apiextractorresult.h> +#include <abstractmetafunction.h> +#include <abstractmetalang.h> +#include <codesnip.h> +#include <exception.h> +#include <messages.h> +#include <textstream.h> +#include <overloaddata.h> +#include <smartpointertypeentry.h> + +#include <QtCore/QDebug> + +using namespace Qt::StringLiterals; + +static const char smartPtrComment[] = + "// Try to find the 'name' attribute, by retrieving the PyObject for " + "the corresponding C++ object held by the smart pointer.\n"; + +static QString smartPointerGetter(const GeneratorContext &context) +{ + const auto te = context.metaClass()->typeEntry(); + Q_ASSERT(te->isSmartPointer()); + return std::static_pointer_cast<const SmartPointerTypeEntry>(te)->getter(); +} + +struct callGetter +{ + explicit callGetter(const GeneratorContext &context) : m_context(context) {} + + const GeneratorContext &m_context; +}; + +TextStream &operator<<(TextStream &str, const callGetter &c) +{ + str << "PyObject_CallMethod(self, \"" << smartPointerGetter(c.m_context) << "\", 0)"; + return str; +} + +// Helpers to collect all smart pointer pointee base classes +static AbstractMetaClassCList + findSmartPointeeBaseClasses(const ApiExtractorResult &api, + const AbstractMetaType &smartPointerType) +{ + AbstractMetaClassCList result; + auto instantiationsTe = smartPointerType.instantiations().at(0).typeEntry(); + auto targetClass = AbstractMetaClass::findClass(api.classes(), instantiationsTe); + if (targetClass != nullptr) + result = targetClass->allTypeSystemAncestors(); + return result; +} + +using ComparisonOperatorList = QList<AbstractMetaFunction::ComparisonOperatorType>; + +// Return the available comparison operators for smart pointers +static ComparisonOperatorList smartPointeeComparisons(const GeneratorContext &context) +{ + Q_ASSERT(context.forSmartPointer()); + auto te = context.preciseType().instantiations().constFirst().typeEntry(); + if (isExtendedCppPrimitive(te)) { // Primitive pointee types have all + return {AbstractMetaFunction::OperatorEqual, + AbstractMetaFunction::OperatorNotEqual, + AbstractMetaFunction::OperatorLess, + AbstractMetaFunction::OperatorLessEqual, + AbstractMetaFunction::OperatorGreater, + AbstractMetaFunction::OperatorGreaterEqual}; + } + + const auto pointeeClass = context.pointeeClass(); + if (!pointeeClass) + return {}; + + ComparisonOperatorList result; + const auto &comparisons = + pointeeClass->operatorOverloads(OperatorQueryOption::SymmetricalComparisonOp); + for (const auto &f : comparisons) { + const auto ct = f->comparisonOperatorType().value(); + if (!result.contains(ct)) + result.append(ct); + } + return result; +} + +std::optional<AbstractMetaType> + CppGenerator::findSmartPointerInstantiation(const SmartPointerTypeEntryCPtr &pointer, + const TypeEntryCPtr &pointee) const +{ + for (const auto &smp : api().instantiatedSmartPointers()) { + const auto &i = smp.type; + if (i.typeEntry() == pointer && i.instantiations().at(0).typeEntry() == pointee) + return i; + } + return {}; +} + +static bool hasParameterPredicate(const AbstractMetaFunctionCPtr &f) +{ + return !f->arguments().isEmpty(); +} + +void CppGenerator::generateSmartPointerClass(TextStream &s, const GeneratorContext &classContext) +{ + s.setLanguage(TextStream::Language::Cpp); + AbstractMetaClassCPtr metaClass = classContext.metaClass(); + const auto typeEntry = std::static_pointer_cast<const SmartPointerTypeEntry>(metaClass->typeEntry()); + const bool hasPointeeClass = classContext.pointeeClass() != nullptr; + const auto smartPointerType = typeEntry->smartPointerType(); + const bool isValueHandle = smartPointerType ==TypeSystem::SmartPointerType::ValueHandle; + + IncludeGroup includes{u"Extra includes"_s, typeEntry->extraIncludes()}; + if (hasPointeeClass) + includes.append(classContext.pointeeClass()->typeEntry()->include()); + includes.includes.append({Include::IncludePath, u"sbksmartpointer.h"_s}); + generateIncludes(s, classContext, {includes}); + + s << '\n'; + + // class inject-code native/beginning + if (!typeEntry->codeSnips().isEmpty()) { + writeClassCodeSnips(s, typeEntry->codeSnips(), + TypeSystem::CodeSnipPositionBeginning, TypeSystem::NativeCode, + classContext); + s << '\n'; + } + + StringStream smd(TextStream::Language::Cpp); + StringStream md(TextStream::Language::Cpp); + StringStream signatureStream(TextStream::Language::Cpp); + + s << openTargetExternC; + + const auto &functionGroups = getFunctionGroups(metaClass); + + // Skip all public methods of the smart pointer except for the special + // methods declared in the type entry. + + auto ctors = metaClass->queryFunctions(FunctionQueryOption::Constructors); + if (!hasPointeeClass && !isValueHandle) { // Cannot generate "int*" + auto end = std::remove_if(ctors.begin(), ctors.end(), hasParameterPredicate); + ctors.erase(end, ctors.end()); + } + + if (!ctors.isEmpty()) { + OverloadData overloadData(ctors, api()); + writeConstructorWrapper(s, overloadData, classContext); + writeSignatureInfo(signatureStream, overloadData); + } + + if (!typeEntry->resetMethod().isEmpty()) { + auto it = functionGroups.constFind(typeEntry->resetMethod()); + if (it == functionGroups.cend()) + throw Exception(msgCannotFindSmartPointerMethod(typeEntry, typeEntry->resetMethod())); + AbstractMetaFunctionCList resets = it.value(); + if (!hasPointeeClass && !isValueHandle) { // Cannot generate "int*" + auto end = std::remove_if(resets.begin(), resets.end(), hasParameterPredicate); + resets.erase(end, resets.end()); + } + if (!resets.isEmpty()) + writeMethodWrapper(s, md, signatureStream, resets, classContext); + } + + auto it = functionGroups.constFind(typeEntry->getter()); + if (it == functionGroups.cend() || it.value().size() != 1) + throw Exception(msgCannotFindSmartPointerGetter(typeEntry)); + + writeMethodWrapper(s, md, signatureStream, it.value(), classContext); + + QStringList optionalMethods; + if (!typeEntry->refCountMethodName().isEmpty()) + optionalMethods.append(typeEntry->refCountMethodName()); + const QString valueCheckMethod = typeEntry->valueCheckMethod(); + if (!valueCheckMethod.isEmpty() && !valueCheckMethod.startsWith(u"operator")) + optionalMethods.append(valueCheckMethod); + if (!typeEntry->nullCheckMethod().isEmpty()) + optionalMethods.append(typeEntry->nullCheckMethod()); + + for (const QString &optionalMethod : optionalMethods) { + auto it = functionGroups.constFind(optionalMethod); + if (it == functionGroups.cend() || it.value().size() != 1) + throw Exception(msgCannotFindSmartPointerMethod(typeEntry, optionalMethod)); + writeMethodWrapper(s, md, signatureStream, it.value(), classContext); + } + + writeCopyFunction(s, md, signatureStream, classContext); + writeSmartPointerDirFunction(s, md, signatureStream, classContext); + + const QString methodsDefinitions = md.toString(); + const QString singleMethodDefinitions = smd.toString(); + + const QString className = chopType(cpythonTypeName(typeEntry)); + + // Write single method definitions + s << singleMethodDefinitions; + + // Write methods definition + writePyMethodDefs(s, className, methodsDefinitions); + + // Write tp_s/getattro function + const auto boolCastOpt = boolCast(metaClass); + writeSmartPointerGetattroFunction(s, classContext, boolCastOpt); + writeSmartPointerSetattroFunction(s, classContext); + + if (boolCastOpt.has_value()) + writeNbBoolFunction(classContext, boolCastOpt.value(), s); + + if (smartPointerType == TypeSystem::SmartPointerType::Shared) + writeSmartPointerRichCompareFunction(s, classContext); + + s << closeExternC; + + if (hasHashFunction(metaClass)) + writeHashFunction(s, classContext); + + // Write tp_traverse and tp_clear functions. + writeTpTraverseFunction(s, metaClass); + writeTpClearFunction(s, metaClass); + + writeClassDefinition(s, metaClass, classContext); + + s << '\n'; + + writeConverterFunctions(s, metaClass, classContext); + writeClassRegister(s, metaClass, classContext, signatureStream); + + // class inject-code native/end + if (!typeEntry->codeSnips().isEmpty()) { + writeClassCodeSnips(s, typeEntry->codeSnips(), + TypeSystem::CodeSnipPositionEnd, TypeSystem::NativeCode, + classContext); + s << '\n'; + } +} + +void CppGenerator::writeSmartPointerConverterFunctions(TextStream &s, + const AbstractMetaType &smartPointerType) const +{ + const auto baseClasses = findSmartPointeeBaseClasses(api(), smartPointerType); + if (baseClasses.isEmpty()) + return; + + auto smartPointerTypeEntry = + std::static_pointer_cast<const SmartPointerTypeEntry>(smartPointerType.typeEntry()); + + // TODO: Missing conversion to smart pointer pointer type: + + s << "// Register smartpointer conversion for all derived classes\n"; + for (const auto &base : baseClasses) { + auto baseTe = base->typeEntry(); + if (smartPointerTypeEntry->matchesInstantiation(baseTe)) { + if (auto opt = findSmartPointerInstantiation(smartPointerTypeEntry, baseTe)) { + const auto smartTargetType = opt.value(); + s << "// SmartPointer derived class: " + << smartTargetType.cppSignature() << "\n"; + writePythonToCppConversionFunctions(s, smartPointerType, + smartTargetType, {}, {}, {}); + } + } + } +} + +void CppGenerator::writeSmartPointerCppSelfConversion(TextStream &s, + const GeneratorContext &context) +{ + Q_ASSERT(context.forSmartPointer()); + s << cpythonWrapperCPtr(context.preciseType(), u"self"_s); +} + +void CppGenerator::writeSmartPointerCppSelfDefinition(TextStream &s, + const GeneratorContext &context, + ErrorReturn errorReturn, + CppSelfDefinitionFlags flags) +{ + Q_ASSERT(context.forSmartPointer()); + writeInvalidPyObjectCheck(s, u"self"_s, errorReturn); + writeCppSelfVarDef(s, flags); + writeSmartPointerCppSelfConversion(s, context); + s << ";\n"; +} + +void CppGenerator::writeSmartPointerConverterInitialization(TextStream &s, + const AbstractMetaType &type) const +{ + const QByteArray cppSignature = type.cppSignature().toUtf8(); + auto writeConversionRegister = [&s](const AbstractMetaType &sourceType, + const QString &targetTypeName, + const QString &targetConverter) + { + const QString sourceTypeName = fixedCppTypeName(sourceType); + const QString toCpp = pythonToCppFunctionName(sourceTypeName, targetTypeName); + const QString isConv = convertibleToCppFunctionName(sourceTypeName, targetTypeName); + + writeAddPythonToCppConversion(s, targetConverter, toCpp, isConv); + }; + + const auto classes = findSmartPointeeBaseClasses(api(), type); + if (classes.isEmpty()) + return; + + auto smartPointerTypeEntry = std::static_pointer_cast<const SmartPointerTypeEntry>(type.typeEntry()); + + s << "// Register SmartPointer converter for type '" << cppSignature << "'." << '\n' + << "///////////////////////////////////////////////////////////////////////////////////////\n\n"; + + for (const auto &base : classes) { + auto baseTe = base->typeEntry(); + if (auto opt = findSmartPointerInstantiation(smartPointerTypeEntry, baseTe)) { + const auto smartTargetType = opt.value(); + s << "// Convert to SmartPointer derived class: [" + << smartTargetType.cppSignature() << "]\n"; + const QString converter = u"Shiboken::Conversions::getConverter(\""_s + + smartTargetType.cppSignature() + u"\")"_s; + writeConversionRegister(type, fixedCppTypeName(smartTargetType), converter); + } else { + s << "// Class not found:" << type.instantiations().at(0).cppSignature(); + } + } + + s << "///////////////////////////////////////////////////////////////////////////////////////" << '\n' << '\n'; +} + +void CppGenerator::writeSmartPointerRichCompareFunction(TextStream &s, + const GeneratorContext &context) const +{ + static const char selfPointeeVar[] = "cppSelfPointee"; + static const char cppArg0PointeeVar[] = "cppArg0Pointee"; + + const auto metaClass = context.metaClass(); + QString baseName = cpythonBaseName(metaClass); + writeRichCompareFunctionHeader(s, baseName, context); + + s << "if ("; + writeTypeCheck(s, context.preciseType(), PYTHON_ARG); + s << ") {\n" << indent; + writeArgumentConversion(s, context.preciseType(), CPP_ARG0, + PYTHON_ARG, ErrorReturn::Default, metaClass); + + const auto te = context.preciseType().typeEntry(); + Q_ASSERT(te->isSmartPointer()); + const auto ste = std::static_pointer_cast<const SmartPointerTypeEntry>(te); + + s << "const auto *" << selfPointeeVar << " = " << CPP_SELF_VAR + << '.' << ste->getter() << "();\n"; + s << "const auto *" << cppArg0PointeeVar << " = " << CPP_ARG0 + << '.' << ste->getter() << "();\n"; + + // If we have an object without any comparisons, only generate a simple + // equality check by pointee address + auto availableOps = smartPointeeComparisons(context); + const bool comparePointeeAddressOnly = availableOps.isEmpty(); + if (comparePointeeAddressOnly) { + availableOps << AbstractMetaFunction::OperatorEqual + << AbstractMetaFunction::OperatorNotEqual; + } else { + // For value types with operators, we complain about nullptr + s << "if (" << selfPointeeVar << " == nullptr || " << cppArg0PointeeVar + << " == nullptr) {\n" << indent + << "PyErr_SetString(PyExc_NotImplementedError, \"nullptr passed to comparison.\");\n" + << ErrorReturn::Default << '\n' << outdent << "}\n"; + } + + s << "bool " << CPP_RETURN_VAR << "= false;\n" + << "switch (op) {\n"; + for (auto op : availableOps) { + s << "case " << AbstractMetaFunction::pythonRichCompareOpCode(op) << ":\n" + << indent << CPP_RETURN_VAR << " = "; + if (comparePointeeAddressOnly) { + s << selfPointeeVar << ' ' << AbstractMetaFunction::cppComparisonOperator(op) + << ' ' << cppArg0PointeeVar << ";\n"; + } else { + // Shortcut for equality: Check pointee address + if (op == AbstractMetaFunction::OperatorEqual + || op == AbstractMetaFunction::OperatorLessEqual + || op == AbstractMetaFunction::OperatorGreaterEqual) { + s << selfPointeeVar << " == " << cppArg0PointeeVar << " || "; + } + // Generate object's comparison + s << "*" << selfPointeeVar << ' ' + << AbstractMetaFunction::cppComparisonOperator(op) << " *" + << cppArg0PointeeVar << ";\n"; + } + s << "break;\n" << outdent; + + } + if (availableOps.size() < 6) { + s << "default:\n" << indent + << richCompareComment + << "return FallbackRichCompare(self, " << PYTHON_ARG << ", op);\n" << outdent; + } + s << "}\n" << PYTHON_RETURN_VAR << " = " << CPP_RETURN_VAR + << " ? Py_True : Py_False;\n" + << "Py_INCREF(" << PYTHON_RETURN_VAR << ");\n" + << outdent << "}\n" + << "return Shiboken::returnFromRichCompare(" << PYTHON_RETURN_VAR << ");\n" + << outdent << "}\n\n"; +} + +void CppGenerator::writeSmartPointerSetattroFunction(TextStream &s, + const GeneratorContext &context) +{ + Q_ASSERT(context.forSmartPointer()); + writeSetattroDefinition(s, context.metaClass()); + s << smartPtrComment + << "if (auto *rawObj = " << callGetter(context) << ") {\n" << indent + << "if (PyObject_HasAttr(rawObj, name) != 0)\n" << indent + << "return PyObject_GenericSetAttr(rawObj, name, value);\n" << outdent + << "Py_DECREF(rawObj);\n" << outdent + << "}\n"; + writeSetattroDefaultReturn(s); +} + +void CppGenerator::writeSmartPointerGetattroFunction(TextStream &s, + const GeneratorContext &context, + const BoolCastFunctionOptional &boolCast) +{ + Q_ASSERT(context.forSmartPointer()); + const auto metaClass = context.metaClass(); + writeGetattroDefinition(s, metaClass); + s << "PyObject *tmp = PyObject_GenericGetAttr(self, name);\n" + << "if (tmp)\n" << indent << "return tmp;\n" << outdent + << "if (PyErr_ExceptionMatches(PyExc_AttributeError) == 0)\n" + << indent << "return nullptr;\n" << outdent + << "PyErr_Clear();\n"; + + if (boolCast.has_value()) { + writeSmartPointerCppSelfDefinition(s, context); + s << "if ("; + writeNbBoolExpression(s, boolCast.value(), true /* invert */); + s << ") {\n" << indent + << R"(PyTypeObject *tp = Py_TYPE(self); +PyErr_Format(PyExc_AttributeError, "Attempt to retrieve '%s' from null object '%s'.", + Shiboken::String::toCString(name), tp->tp_name); +return nullptr; +)" << outdent << "}\n"; + } + + // This generates the code which dispatches access to member functions + // and fields from the smart pointer to its pointee. + s << smartPtrComment + << "if (auto *rawObj = " << callGetter(context) << ") {\n" << indent + << "if (auto *attribute = PyObject_GetAttr(rawObj, name))\n" + << indent << "tmp = attribute;\n" << outdent + << "Py_DECREF(rawObj);\n" << outdent + << "}\n" + << "if (!tmp) {\n" << indent + << R"(PyTypeObject *tp = Py_TYPE(self); +PyErr_Format(PyExc_AttributeError, + "'%.50s' object has no attribute '%.400s'", + tp->tp_name, Shiboken::String::toCString(name)); +)" << outdent + << "}\n" + << "return tmp;\n" << outdent << "}\n\n"; +} + +QString CppGenerator::writeSmartPointerReprFunction(TextStream &s, + const GeneratorContext &context) +{ + const auto metaClass = context.metaClass(); + QString funcName = writeReprFunctionHeader(s, context); + s << "Shiboken::AutoDecRef pointee(" << callGetter(context) << ");\n" + << "return Shiboken::SmartPointer::repr(self, pointee);\n"; + writeReprFunctionFooter(s); + return funcName; +} + +QString CppGenerator::writeSmartPointerDirFunction(TextStream &s, TextStream &definitionStream, + TextStream &signatureStream, + const GeneratorContext &context) +{ + QString funcName = cpythonBaseName(context.metaClass()) + u"__dir__"_s; + + signatureStream << fullPythonClassName(context.metaClass()) << ".__dir__()\n"; + definitionStream << PyMethodDefEntry{u"__dir__"_s, funcName, {"METH_NOARGS"_ba}, {}} + << ",\n"; + + s << "extern \"C\"\n{\n" + << "static PyObject *" << funcName << "(PyObject *self)\n{\n" << indent + << "Shiboken::AutoDecRef pointee(" << callGetter(context) << ");\n" + << "return Shiboken::SmartPointer::dir(self, pointee);\n" + << outdent << "}\n} // extern C\n\n"; + return funcName; +} diff --git a/sources/shiboken6/generator/shiboken/ctypenames.h b/sources/shiboken6/generator/shiboken/ctypenames.h new file mode 100644 index 000000000..f665b30ff --- /dev/null +++ b/sources/shiboken6/generator/shiboken/ctypenames.h @@ -0,0 +1,31 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef CTYPENAMES_H +#define CTYPENAMES_H + +#include <QtCore/QString> + +constexpr auto boolT = QLatin1StringView("bool"); +constexpr auto intT = QLatin1StringView("int"); +constexpr auto unsignedT = QLatin1StringView("unsigned"); +constexpr auto unsignedIntT = QLatin1StringView("unsigned int"); +constexpr auto longT = QLatin1StringView("long"); +constexpr auto unsignedLongT = QLatin1StringView("unsigned long"); +constexpr auto shortT = QLatin1StringView("short"); +constexpr auto unsignedShortT = QLatin1StringView("unsigned short"); +constexpr auto unsignedCharT = QLatin1StringView("unsigned char"); +constexpr auto longLongT = QLatin1StringView("long long"); +constexpr auto unsignedLongLongT = QLatin1StringView("unsigned long long"); +constexpr auto charT = QLatin1StringView("char"); +constexpr auto floatT = QLatin1StringView("float"); +constexpr auto doubleT = QLatin1StringView("double"); +constexpr auto constCharPtrT = QLatin1StringView("const char*"); + +constexpr auto qByteArrayT = QLatin1StringView("QByteArray"); +constexpr auto qMetaObjectT = QLatin1StringView("QMetaObject"); +constexpr auto qObjectT = QLatin1StringView("QObject"); +constexpr auto qStringT = QLatin1StringView("QString"); +constexpr auto qVariantT = QLatin1StringView("QVariant"); + +#endif // CTYPENAMES_H diff --git a/sources/shiboken6/generator/shiboken/generatorargument.cpp b/sources/shiboken6/generator/shiboken/generatorargument.cpp new file mode 100644 index 000000000..e81ad0797 --- /dev/null +++ b/sources/shiboken6/generator/shiboken/generatorargument.cpp @@ -0,0 +1,110 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "generatorargument.h" +#include <abstractmetatype.h> +#include <messages.h> +#include <typesystem.h> + +#include <QtCore/QDebug> +#include <QtCore/QSet> + +static bool isCppPrimitiveString(const AbstractMetaType &type) +{ + return type.referenceType() == NoReference && type.indirections() == 1 + && AbstractMetaType::cppSignedCharTypes().contains(type.name()); +} + +GeneratorArgument GeneratorArgument::fromMetaType(const AbstractMetaType &type) +{ + GeneratorArgument result; + + const auto typeEntry = type.typeEntry(); + if (typeEntry->isCustom() || typeEntry->isVarargs()) + return result; + + result.indirections = -type.indirectionsV().size(); + if (isCppPrimitiveString(type) + || type.isVoidPointer() + || type.typeUsagePattern() == AbstractMetaType::NativePointerAsArrayPattern) { + result.indirections += 1; + } + + if (typeEntry->isEnum()) { + result.type = Type::Enum; + } else if (typeEntry->isFlags()) { + result.type = Type::Flags; + } else if (typeEntry->isContainer()) { + result.type = Type::Container; + } else { + if (typeEntry->isPrimitive()) + result.type = Type::Primitive; + + const AbstractMetaTypeList &nestedArrayTypes = type.nestedArrayTypes(); + if (!nestedArrayTypes.isEmpty()) { + if (nestedArrayTypes.constLast().isCppPrimitive()) { + result.type = Type::CppPrimitiveArray; + } else { + static QSet<QString> warnedTypes; + const QString &signature = type.cppSignature(); + if (!warnedTypes.contains(signature)) { + warnedTypes.insert(signature); + qWarning("%s", qPrintable(msgUnknownArrayPointerConversion(signature))); + } + result.indirections -= 1; + } + } + } + + if (result.type == Type::Other || result.type == Type::Primitive) { + if (type.valueTypeWithCopyConstructorOnlyPassed()) { + result.flags.setFlag(Flag::TreatAsPointer); + } else if ((type.isObjectType() || type.isPointer()) + && !type.isUserPrimitive() && !type.isExtendedCppPrimitive()) { + result.flags.setFlag(Flag::PointerOrObjectType); + } else if (type.referenceType() == LValueReference + && !type.isUserPrimitive() + && !type.isExtendedCppPrimitive()) { + result.flags.setFlag(Flag::MayHaveImplicitConversion); + } + } + + // For implicit conversions or containers, either value or pointer conversion + // may occur. An implicit conversion uses value conversion whereas the object + // itself uses pointer conversion. For containers, the PyList/container + // conversion is by value whereas opaque containers use pointer conversion. + // For a container passed by pointer, a local variable is also needed. + if (result.flags.testFlag(Flag::MayHaveImplicitConversion) + || type.generateOpaqueContainer() + || (result.type == Type::Container && result.indirections != 0)) { + result.flags.setFlag(Flag::ValueOrPointer); + } + + if (result.type == Type::CppPrimitiveArray) { + result.conversion = Conversion::CppPrimitiveArray; + } else if (result.flags.testFlag(Flag::ValueOrPointer)) { + result.conversion = Conversion::ValueOrPointer; + ++result.indirections; + } else if (result.flags.testAnyFlags(Flag::TreatAsPointer | Flag::PointerOrObjectType)) { + result.conversion = Conversion::Pointer; + ++result.indirections; + } + + return result; +} + +QDebug operator<<(QDebug debug, const GeneratorArgument &a) +{ + QDebugStateSaver saver(debug); + debug.noquote(); + debug.nospace(); + debug << "GeneratorArgument(" << a.type; + if (a.conversion != GeneratorArgument::Conversion::Default) + debug << ", conversion=" << a.conversion; + if (a.flags) + debug << ", flags(" << a.flags; + if (a.indirections != 0) + debug << ", indirections=" << a.indirections; + debug << ')'; + return debug; +} diff --git a/sources/shiboken6/generator/shiboken/generatorargument.h b/sources/shiboken6/generator/shiboken/generatorargument.h new file mode 100644 index 000000000..385ad0f63 --- /dev/null +++ b/sources/shiboken6/generator/shiboken/generatorargument.h @@ -0,0 +1,60 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef GENERATORARGUMENT_H +#define GENERATORARGUMENT_H + +#include <QtCore/QFlags> +#include <QtCore/qobjectdefs.h> + +QT_FORWARD_DECLARE_CLASS(QDebug) + +class AbstractMetaType; + +/// A struct containing information on how the generator processes a function argument. +struct GeneratorArgument +{ + Q_GADGET + +public: + enum class Type { + Other, + Primitive, + Enum, + Flags, + Container, + CppPrimitiveArray + }; + Q_ENUM(Type) + + enum class Conversion { + Default, + CppPrimitiveArray, // Similar to Default except default values + Pointer, + ValueOrPointer + }; + Q_ENUM(Conversion) + + enum class Flag { + TreatAsPointer = 0x1, + PointerOrObjectType = 0x2, + MayHaveImplicitConversion = 0x4, + ValueOrPointer = 0x8, + }; + Q_ENUM(Flag) + Q_DECLARE_FLAGS(Flags, Flag) + + static GeneratorArgument fromMetaType(const AbstractMetaType &type); + + Flags flags; + /// Indirections from generated "cppArg<n>" variable to function argument. + qsizetype indirections = 0; + Type type = Type::Other; + Conversion conversion = Conversion::Default; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(GeneratorArgument::Flags) + +QDebug operator<<(QDebug debug, const GeneratorArgument &a); + +#endif // GENERATORARGUMENT_H diff --git a/sources/shiboken6/generator/shiboken/generatorstrings.h b/sources/shiboken6/generator/shiboken/generatorstrings.h new file mode 100644 index 000000000..9ce91e599 --- /dev/null +++ b/sources/shiboken6/generator/shiboken/generatorstrings.h @@ -0,0 +1,39 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef GENERATORSTRINGS_H +#define GENERATORSTRINGS_H + +#include <QtCore/QString> + +QString CPP_ARG_N(int i); +QString CPP_ARG_REMOVED(int i); + +constexpr auto CPP_RETURN_VAR = QLatin1StringView("cppResult"); +constexpr auto CPP_SELF_VAR = QLatin1StringView("cppSelf"); +constexpr auto CPP_ARG = QLatin1StringView("cppArg"); +constexpr auto NULL_PTR = QLatin1StringView("nullptr"); +constexpr auto PYTHON_ARG = QLatin1StringView("pyArg"); +constexpr auto PYTHON_ARGS = QLatin1StringView("pyArgs"); +constexpr auto PYTHON_OVERRIDE_VAR = QLatin1StringView("pyOverride"); +constexpr auto PYTHON_RETURN_VAR = QLatin1StringView("pyResult"); +constexpr auto PYTHON_SELF_VAR = QLatin1StringView("self"); +constexpr auto PYTHON_TO_CPP_VAR = QLatin1StringView("pythonToCpp"); + +constexpr auto CONV_RULE_OUT_VAR_SUFFIX = QLatin1StringView("_out"); +constexpr auto BEGIN_ALLOW_THREADS + = QLatin1StringView("PyThreadState *_save = PyEval_SaveThread(); // Py_BEGIN_ALLOW_THREADS"); +constexpr auto END_ALLOW_THREADS + = QLatin1StringView("PyEval_RestoreThread(_save); // Py_END_ALLOW_THREADS"); + +constexpr auto REPR_FUNCTION = QLatin1StringView("__repr__"); + +constexpr auto CPP_ARG0 = QLatin1StringView("cppArg0"); + +extern const char *const METHOD_DEF_SENTINEL; +extern const char *const PYTHON_TO_CPPCONVERSION_STRUCT; +extern const char *const openTargetExternC; +extern const char *const closeExternC; +extern const char *const richCompareComment; + +#endif // GENERATORSTRINGS_H diff --git a/sources/shiboken6/generator/shiboken/headergenerator.cpp b/sources/shiboken6/generator/shiboken/headergenerator.cpp new file mode 100644 index 000000000..35d0d114f --- /dev/null +++ b/sources/shiboken6/generator/shiboken/headergenerator.cpp @@ -0,0 +1,964 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "headergenerator.h" +#include "configurablescope.h" +#include "generatorcontext.h" +#include <apiextractorresult.h> +#include <abstractmetaargument.h> +#include <abstractmetaenum.h> +#include <abstractmetafield.h> +#include <abstractmetafunction.h> +#include <abstractmetalang.h> +#include <abstractmetalang_helpers.h> +#include <codesnip.h> +#include <clangparser/compilersupport.h> +#include <exception.h> +#include <typedatabase.h> +#include <reporthandler.h> +#include <textstream.h> +#include <fileout.h> +#include "containertypeentry.h" +#include "enumtypeentry.h" +#include "flagstypeentry.h" +#include <messages.h> +#include "namespacetypeentry.h" +#include "primitivetypeentry.h" +#include "typedefentry.h" +#include "typesystemtypeentry.h" + +#include "qtcompat.h" + +#include <algorithm> +#include <set> + +#include <QtCore/QDir> +#include <QtCore/QTextStream> +#include <QtCore/QVariant> +#include <QtCore/QDebug> + +using namespace Qt::StringLiterals; + +struct IndexValue +{ + QString name; // "SBK_..." + int value; + QString comment; +}; + +TextStream &operator<<(TextStream &s, const IndexValue &iv) +{ + s << " " << AlignedField(iv.name, 56) << " = " << iv.value << ','; + if (!iv.comment.isEmpty()) + s << " // " << iv.comment; + s << '\n'; + return s; +} + +// PYSIDE-504: Handling the "protected hack" +// The problem: Creating wrappers when the class has private destructors. +// You can see an example on Windows in qclipboard_wrapper.h and others. +// Simply search for the text "// C++11: need to declare (unimplemented) destructor". +// The protected hack is the definition "#define protected public". +// For most compilers, this "hack" is enabled, because the problem of private +// destructors simply vanishes. +// +// If one does not want to use this hack, then a new problem arises: +// C++11 requires that a destructor is declared in a wrapper class when it is +// private in the base class. There is no implementation allowed! +// +// Unfortunately, MSVC in recent versions supports C++11, and due to restrictive +// rules, it is impossible to use the hack with this compiler. +// More unfortunate: Clang, when C++11 is enabled, also enforces a declaration +// of a private destructor, but it falsely then creates a linker error! +// +// Originally, we wanted to remove the protected hack. But due to the Clang +// problem, we gave up on removal of the protected hack and use it always +// when we can. This might change again when the Clang problem is solved. + +static bool alwaysGenerateDestructorDeclaration() +{ + return clang::compiler() == Compiler::Msvc; +} + +const char *HeaderGenerator::protectedHackDefine = R"(// Workaround to access protected functions +#ifndef protected +# define protected public +#endif + +)"; + +QString HeaderGenerator::fileNameForContext(const GeneratorContext &context) const +{ + return headerFileNameForContext(context); +} + +void HeaderGenerator::writeCopyCtor(TextStream &s, + const AbstractMetaClassCPtr &metaClass) +{ + s << wrapperName(metaClass) << "(const " << metaClass->qualifiedCppName() + << "& self) : " << metaClass->qualifiedCppName() << "(self)\n{\n}\n\n"; +} + +static void writeProtectedEnums(TextStream &s, const AbstractMetaClassCPtr &metaClass) +{ + const QString name = metaClass->qualifiedCppName(); + for (const auto &e : metaClass->enums()) { + if (e.isProtected()) + s << "using " << name << "::" << e.name() << ";\n"; + } +} + +void HeaderGenerator::generateClass(TextStream &s, const GeneratorContext &classContext) +{ + const AbstractMetaClassCPtr metaClass = classContext.metaClass(); + + // write license comment + s << licenseComment(); + + QString wrapperName = classContext.effectiveClassName(); + QString outerHeaderGuard = getFilteredCppSignatureString(wrapperName); + + // Header + s << "#ifndef SBK_" << outerHeaderGuard << "_H\n"; + s << "#define SBK_" << outerHeaderGuard << "_H\n\n"; + + if (!avoidProtectedHack()) + s << protectedHackDefine; + + // Includes + s << metaClass->typeEntry()->include() << '\n'; + for (auto &inst : metaClass->templateBaseClassInstantiations()) + s << inst.typeEntry()->include(); + + if (classContext.useWrapper()) + writeWrapperClass(s, wrapperName, classContext); + + s << "#endif // SBK_" << outerHeaderGuard << "_H\n\n"; +} + +void HeaderGenerator::writeWrapperClass(TextStream &s, + const QString &wrapperName, + const GeneratorContext &classContext) const +{ + const auto metaClass = classContext.metaClass(); + + if (avoidProtectedHack()) { + const auto includeGroups = classIncludes(metaClass); + for( const auto &includeGroup : includeGroups) + s << includeGroup; + } + + if (usePySideExtensions() && isQObject(metaClass)) + s << "namespace PySide { class DynamicQMetaObject; }\n\n"; + + writeWrapperClassDeclaration(s, wrapperName, classContext); + + // PYSIDE-500: Use also includes for inherited wrapper classes other + // modules, because without the protected hack, we sometimes need to + // cast inherited wrappers. CppGenerator generates include statements for + // the classes of the current module. For other modules, we insert the + // declarations as recursive headers, since wrapper headers are not + // installed. This keeps the file structure as simple as before the + // enhanced inheritance. + if (avoidProtectedHack()) { + const auto &baseClasses = allBaseClasses(classContext.metaClass()); + for (const auto &baseClass : baseClasses) { + const auto gen = baseClass->typeEntry()->codeGeneration(); + if (gen == TypeEntry::GenerateForSubclass) { // other module + const auto baseContext = contextForClass(baseClass); + if (baseContext.useWrapper()) + writeInheritedWrapperClassDeclaration(s, baseContext); + } + } + } +} + +void HeaderGenerator::writeInheritedWrapperClassDeclaration(TextStream &s, + const GeneratorContext &classContext) const +{ + const QString wrapperName = classContext.effectiveClassName(); + const QString innerHeaderGuard = + getFilteredCppSignatureString(wrapperName).toUpper(); + + s << "# ifndef SBK_" << innerHeaderGuard << "_H\n" + << "# define SBK_" << innerHeaderGuard << "_H\n\n" + << "// Inherited base class:\n"; + + writeWrapperClassDeclaration(s, wrapperName, classContext); + + s << "# endif // SBK_" << innerHeaderGuard << "_H\n\n"; +} + +void HeaderGenerator::writeWrapperClassDeclaration(TextStream &s, + const QString &wrapperName, + const GeneratorContext &classContext) const +{ + const AbstractMetaClassCPtr metaClass = classContext.metaClass(); + const auto typeEntry = metaClass->typeEntry(); + InheritedOverloadSet inheritedOverloads; + + // write license comment + s << licenseComment(); + + // Class + s << "class " << wrapperName + << " : public " << metaClass->qualifiedCppName() + << "\n{\npublic:\n" << indent + << wrapperName << "(const " << wrapperName << " &) = delete;\n" + << wrapperName << "& operator=(const " << wrapperName << " &) = delete;\n" + << wrapperName << '(' << wrapperName << " &&) = delete;\n" + << wrapperName << "& operator=(" << wrapperName << " &&) = delete;\n\n"; + + // Make protected enums accessible + if (avoidProtectedHack()) { + recurseClassHierarchy(metaClass, [&s] (const AbstractMetaClassCPtr &metaClass) { + writeProtectedEnums(s, metaClass); + return false; + }); + } + + if (avoidProtectedHack() && metaClass->hasProtectedFields()) { + s << "\n// Make protected fields accessible\n"; + const QString name = metaClass->qualifiedCppName(); + for (const auto &f : metaClass->fields()) { + if (f.isProtected()) + s << "using " << name << "::" << f.originalName() << ";\n"; + } + s << '\n'; + } + + int maxOverrides = 0; + for (const auto &func : metaClass->functions()) { + const auto generation = functionGeneration(func); + writeFunction(s, func, &inheritedOverloads, generation); + // PYSIDE-803: Build a boolean cache for unused overrides. + if (generation.testFlag(FunctionGenerationFlag::VirtualMethod)) + maxOverrides++; + } + if (!maxOverrides) + maxOverrides = 1; + + //destructor + // PYSIDE-504: When C++ 11 is used, then the destructor must always be declared. + if (!avoidProtectedHack() || !metaClass->hasPrivateDestructor() + || alwaysGenerateDestructorDeclaration()) { + if (avoidProtectedHack() && metaClass->hasPrivateDestructor()) + s << "// C++11: need to declare (unimplemented) destructor because " + "the base class destructor is private.\n"; + s << '~' << wrapperName << "()"; + if (metaClass->hasVirtualDestructor()) + s << " override"; + s << ";\n"; + } + + writeClassCodeSnips(s, typeEntry->codeSnips(), + TypeSystem::CodeSnipPositionDeclaration, TypeSystem::NativeCode, + classContext); + + if (shouldGenerateMetaObjectFunctions(metaClass)) { + s << R"( +const ::QMetaObject * metaObject() const override; +int qt_metacall(QMetaObject::Call call, int id, void **args) override; +void *qt_metacast(const char *_clname) override; +)"; + } + + if (!inheritedOverloads.isEmpty()) { + s << "// Inherited overloads, because the using keyword sux\n"; + for (const auto &func : std::as_const(inheritedOverloads)) + writeMemberFunctionWrapper(s, func); + } + + if (usePySideExtensions()) + s << "static void pysideInitQtMetaTypes();\n"; + + s << "void resetPyMethodCache();\n" + << outdent << "private:\n" << indent; + + if (!metaClass->userAddedPythonOverrides().isEmpty()) { + for (const auto &f : metaClass->userAddedPythonOverrides()) + s << functionSignature(f, {}, {}, Generator::OriginalTypeDescription) << ";\n"; + s << '\n'; + } + + s << "mutable bool m_PyMethodCache[" << maxOverrides << "];\n" + << outdent << "};\n\n"; +} + +// Write an inline wrapper around a function +void HeaderGenerator::writeMemberFunctionWrapper(TextStream &s, + const AbstractMetaFunctionCPtr &func, + const QString &postfix) const +{ + Q_ASSERT(!func->isConstructor() && !func->isOperatorOverload()); + s << "inline "; + s << functionSignature(func, {}, postfix, Generator::OriginalTypeDescription) + << " { "; + if (!func->isVoid()) + s << "return "; + if (!func->isAbstract()) { + // Use implementingClass() in case of multiple inheritance (for example + // function setProperty() being inherited from QObject and + // QDesignerPropertySheetExtension). + auto klass = func->implementingClass(); + if (klass == nullptr) + klass = func->ownerClass(); + s << klass->qualifiedCppName() << "::"; + } + s << func->originalName() << '('; + const AbstractMetaArgumentList &arguments = func->arguments(); + for (qsizetype i = 0, size = arguments.size(); i < size; ++i) { + if (i > 0) + s << ", "; + const AbstractMetaArgument &arg = arguments.at(i); + const auto &type = arg.type(); + TypeEntryCPtr enumTypeEntry; + if (type.isFlags()) + enumTypeEntry = std::static_pointer_cast<const FlagsTypeEntry>(type.typeEntry())->originator(); + else if (type.isEnum()) + enumTypeEntry = type.typeEntry(); + if (enumTypeEntry) { + s << type.cppSignature() << '(' << arg.name() << ')'; + } else if (type.passByValue() && type.isUniquePointer()) { + s << stdMove(arg.name()); + } else { + s << arg.name(); + } + } + s << "); }\n"; +} + +void HeaderGenerator::writeFunction(TextStream &s, const AbstractMetaFunctionCPtr &func, + InheritedOverloadSet *inheritedOverloads, + FunctionGeneration generation) const +{ + + // do not write copy ctors here. + if (generation.testFlag(FunctionGenerationFlag::WrapperSpecialCopyConstructor)) { + writeCopyCtor(s, func->ownerClass()); + return; + } + + if (generation.testFlag(FunctionGenerationFlag::ProtectedWrapper)) + writeMemberFunctionWrapper(s, func, u"_protected"_s); + + if (generation.testFlag(FunctionGenerationFlag::WrapperConstructor)) { + Options option = func->hasSignatureModifications() + ? Generator::OriginalTypeDescription : Generator::NoOption; + s << functionSignature(func, {}, {}, option) << ";\n"; + return; + } + + const bool isVirtual = generation.testFlag(FunctionGenerationFlag::VirtualMethod); + if (isVirtual) { + s << functionSignature(func, {}, {}, Generator::OriginalTypeDescription) + << " override;\n"; + } + + // Check if this method hide other methods in base classes + if (isVirtual) { + for (const auto &f : func->ownerClass()->functions()) { + if (f != func + && !f->isConstructor() + && !f->isPrivate() + && !f->isVirtual() + && !f->isAbstract() + && !f->isStatic() + && f->name() == func->name()) { + inheritedOverloads->insert(f); + } + } + + // TODO: when modified an abstract method ceases to be virtual but stays abstract + //if (func->isModifiedRemoved() && func->isAbstract()) { + //} + } +} + +// Find equivalent typedefs "using Foo=QList<int>", "using Bar=QList<int>" +static AbstractMetaClassCPtr + findEquivalentTemplateTypedef(const AbstractMetaClassCList &haystack, + const AbstractMetaClassCPtr &needle) +{ + auto templateBaseClass = needle->templateBaseClass(); + const auto &instantiations = needle->templateBaseClassInstantiations(); + for (const auto &candidate : haystack) { + if (candidate->isTypeDef() + && candidate->templateBaseClass() == templateBaseClass + && candidate->templateBaseClassInstantiations() == instantiations) { + return candidate; + } + } + return nullptr; +} + +void HeaderGenerator::collectTypeEntryTypeIndexes(const ApiExtractorResult &api, + const TypeEntryCPtr &typeEntry, + IndexValues *indexValues) +{ + if (!typeEntry || !typeEntry->generateCode()) + return; + const int typeIndex = typeEntry->sbkIndex(); + indexValues->append({getTypeIndexVariableName(typeEntry), typeIndex, {}}); + + if (typeEntry->isComplex()) { + // For a typedef "using Foo=QList<int>", write a type index + // SBK_QLIST_INT besides SBK_FOO which is then matched by function + // argument. Check against duplicate typedefs for the same types. + const auto cType = std::static_pointer_cast<const ComplexTypeEntry>(typeEntry); + if (cType->baseContainerType()) { + auto metaClass = AbstractMetaClass::findClass(api.classes(), cType); + Q_ASSERT(metaClass != nullptr); + if (metaClass->isTypeDef() + && metaClass->templateBaseClass() != nullptr + && findEquivalentTemplateTypedef(m_alternateTemplateIndexes, + metaClass) == nullptr) { + const QString indexVariable = + getTypeAlternateTemplateIndexVariableName(metaClass); + indexValues->append({indexVariable, typeIndex, {}}); + m_alternateTemplateIndexes.append(m_alternateTemplateIndexes); + } + } + } + if (typeEntry->isEnum()) { + auto ete = std::static_pointer_cast<const EnumTypeEntry>(typeEntry); + if (ete->flags()) + collectTypeEntryTypeIndexes(api, ete->flags(), indexValues); + } +} + +void HeaderGenerator::collectClassTypeIndexes(const ApiExtractorResult &api, + const AbstractMetaClassCPtr &metaClass, + IndexValues *indexValues) +{ + auto typeEntry = metaClass->typeEntry(); + if (!typeEntry->generateCode()) + return; + // enum indices are required for invisible namespaces as well. + for (const AbstractMetaEnum &metaEnum : metaClass->enums()) { + if (!metaEnum.isPrivate()) + collectTypeEntryTypeIndexes(api, metaEnum.typeEntry(), indexValues); + } + if (NamespaceTypeEntry::isVisibleScope(typeEntry)) + collectTypeEntryTypeIndexes(api, typeEntry, indexValues); +} + +// Format the typedefs for the typedef entries to be generated +static void formatTypeDefEntries(TextStream &s) +{ + QList<TypedefEntryCPtr> entries; + const auto typeDbEntries = TypeDatabase::instance()->typedefEntries(); + for (auto it = typeDbEntries.cbegin(), end = typeDbEntries.cend(); it != end; ++it) { + if (it.value()->generateCode() != 0) + entries.append(it.value()); + } + if (entries.isEmpty()) + return; + s << "\n// typedef entries\n"; + for (const auto &e : entries) { + const QString name = e->qualifiedCppName(); + // Fixme: simplify by using nested namespaces in C++ 17. + const auto components = QStringView{name}.split(u"::"); + const auto nameSpaceCount = components.size() - 1; + for (qsizetype n = 0; n < nameSpaceCount; ++n) + s << "namespace " << components.at(n) << " {\n"; + s << "using " << components.constLast() << " = " << e->sourceType() << ";\n"; + for (qsizetype n = 0; n < nameSpaceCount; ++n) + s << "}\n"; + } + s << '\n'; +} + +// Helpers for forward-declaring classes in the module header for the +// specialization of the SbkType template functions. This is possible if the +// class does not have inner types or enums which need to be known. +static bool canForwardDeclare(const AbstractMetaClassCPtr &c) +{ + if (c->isNamespace() || !c->enums().isEmpty() + || !c->innerClasses().isEmpty() || c->isTypeDef()) { + return false; + } + if (auto encl = c->enclosingClass()) + return encl->isNamespace(); + return true; +} + +static void writeForwardDeclaration(TextStream &s, const AbstractMetaClassCPtr &c) +{ + Q_ASSERT(!c->isNamespace()); + const bool isStruct = c->attributes().testFlag(AbstractMetaClass::Struct); + s << (isStruct ? "struct " : "class "); + // Do not use name as this can be modified/renamed for target lang. + const QString qualifiedCppName = c->qualifiedCppName(); + const auto lastQualifier = qualifiedCppName.lastIndexOf(u':'); + if (lastQualifier != -1) + s << QStringView{qualifiedCppName}.mid(lastQualifier + 1); + else + s << qualifiedCppName; + s << ";\n"; +} + +// Helpers for writing out namespaces hierarchically when writing class +// forward declarations to the module header. Ensure inline namespaces +// are marked as such (else clang complains) and namespaces are ordered. +struct NameSpace { + AbstractMetaClassCPtr nameSpace; + AbstractMetaClassCList classes; +}; + +static bool operator<(const NameSpace &n1, const NameSpace &n2) +{ + return n1.nameSpace->name() < n2.nameSpace->name(); +} + +using NameSpaces = QList<NameSpace>; + +static qsizetype indexOf(const NameSpaces &nsps, const AbstractMetaClassCPtr &needle) +{ + for (qsizetype i = 0, count = nsps.size(); i < count; ++i) { + if (nsps.at(i).nameSpace == needle) + return i; + } + return -1; +} + +static void writeNamespaceForwardDeclarationRecursion(TextStream &s, qsizetype idx, + const NameSpaces &nameSpaces) +{ + auto &root = nameSpaces.at(idx); + s << '\n'; + if (root.nameSpace->isInlineNamespace()) + s << "inline "; + s << "namespace " << root.nameSpace->name() << " {\n" << indent; + for (const auto &c : root.classes) + writeForwardDeclaration(s, c); + + for (qsizetype i = 0, count = nameSpaces.size(); i < count; ++i) { + if (i != idx && nameSpaces.at(i).nameSpace->enclosingClass() == root.nameSpace) + writeNamespaceForwardDeclarationRecursion(s, i, nameSpaces); + } + s << outdent << "}\n"; +} + +static void writeForwardDeclarations(TextStream &s, + const AbstractMetaClassCList &classList) +{ + NameSpaces nameSpaces; + + s << '\n'; + auto typeSystemEntry = TypeDatabase::instance()->defaultTypeSystemType(); + if (!typeSystemEntry->namespaceBegin().isEmpty()) + s << typeSystemEntry->namespaceBegin() << '\n'; + + for (const auto &c : classList) { + if (auto encl = c->enclosingClass()) { + Q_ASSERT(encl->isNamespace()); + auto idx = indexOf(nameSpaces, encl); + if (idx != -1) { + nameSpaces[idx].classes.append(c); + } else { + nameSpaces.append(NameSpace{encl, {c}}); + for (auto enclNsp = encl->enclosingClass(); enclNsp; + enclNsp = enclNsp->enclosingClass()) { + idx = indexOf(nameSpaces, enclNsp); + if (idx == -1) + nameSpaces.append(NameSpace{enclNsp, {}}); + } + } + } else { + writeForwardDeclaration(s, c); + } + } + + std::sort(nameSpaces.begin(), nameSpaces.end()); + + // Recursively write out namespaces starting at the root elements. + for (qsizetype i = 0, count = nameSpaces.size(); i < count; ++i) { + const auto &nsp = nameSpaces.at(i); + if (nsp.nameSpace->enclosingClass() == nullptr) + writeNamespaceForwardDeclarationRecursion(s, i, nameSpaces); + } + + if (!typeSystemEntry->namespaceEnd().isEmpty()) + s << typeSystemEntry->namespaceEnd() << '\n'; +} + +// Include parameters required for the module/private module header + +using ConditionalIncludeMap = QMap<QString, IncludeGroup>; + +static TextStream &operator<<(TextStream &s, const ConditionalIncludeMap &m) +{ + for (auto it = m.cbegin(), end = m.cend(); it != end; ++it) + s << it.key() << '\n' << it.value() << "#endif\n"; + return s; +} + +struct ModuleHeaderParameters +{ + AbstractMetaClassCList forwardDeclarations; + std::set<Include> includes; + ConditionalIncludeMap conditionalIncludes; + QString typeFunctions; +}; + +HeaderGenerator::IndexValues + HeaderGenerator::collectTypeIndexes(const AbstractMetaClassCList &classList) +{ + IndexValues result; + + for (const auto &metaClass : classList) + collectClassTypeIndexes(api(), metaClass, &result); + + for (const AbstractMetaEnum &metaEnum : api().globalEnums()) + collectTypeEntryTypeIndexes(api(), metaEnum.typeEntry(), &result); + + // Write the smart pointer define indexes. + int smartPointerCountIndex = getMaxTypeIndex(); + int smartPointerCount = 0; + for (const auto &smp : api().instantiatedSmartPointers()) { + QString indexName = getTypeIndexVariableName(smp.type); + result.append({indexName, smartPointerCountIndex, smp.type.cppSignature()}); + // Add a the same value for const pointees (shared_ptr<const Foo>). + const auto ptrName = smp.type.typeEntry()->entryName(); + const auto pos = indexName.indexOf(ptrName, 0, Qt::CaseInsensitive); + if (pos >= 0) { + indexName.insert(pos + ptrName.size() + 1, u"const"_s); + result.append({indexName, smartPointerCountIndex, "(const)"_L1}); + } + ++smartPointerCountIndex; + ++smartPointerCount; + } + result.append({"SBK_"_L1 + moduleName() + "_IDX_COUNT"_L1, + getMaxTypeIndex() + smartPointerCount, {}}); + return result; +} + +HeaderGenerator::IndexValues HeaderGenerator::collectConverterIndexes() const +{ + IndexValues result; + const auto &primitives = primitiveTypes(); + int pCount = 0; + for (const auto &ptype : primitives) { + // Note: do not generate indices for typedef'd primitive types as + // they'll use the primitive type converters instead, so we + // don't need to create any other. + if (ptype->generateCode() && ptype->customConversion() != nullptr) + result.append({getTypeIndexVariableName(ptype), pCount++, {}}); + } + + for (const AbstractMetaType &container : api().instantiatedContainers()) { + result.append({getTypeIndexVariableName(container), + pCount++, container.cppSignature()}); + } + + // Because on win32 the compiler will not accept a zero length array. + if (pCount == 0) + pCount++; + result.append({"SBK_"_L1 + moduleName() + "_CONVERTERS_IDX_COUNT"_L1, + pCount, {}}); + return result; +} + +// PYSIDE-2404: Write the enums in unchanged case for reuse in type imports. +// For conpatibility, we create them in uppercase, too and with +// doubled index for emulating the former type-only case. +// +// FIXME: Remove in PySide 7. (See the note in `parser.py`) +// +static IndexValue typeIndexUpper(struct IndexValue const &ti) +{ + QString modi = ti.name.toUpper(); + if (modi == ti.name) + modi = u"// "_s + modi; + return {modi, ti.value * 2, ti.comment}; +} + +bool HeaderGenerator::finishGeneration() +{ + // Generate the main header for this module. This header should be included + // by binding modules extending on top of this one. + ModuleHeaderParameters parameters; + ModuleHeaderParameters privateParameters; + StringStream macrosStream(TextStream::Language::Cpp); + + const auto snips = TypeDatabase::instance()->defaultTypeSystemType()->codeSnips(); + writeModuleCodeSnips(macrosStream, snips, TypeSystem::CodeSnipPositionDeclaration, + TypeSystem::TargetLangCode); + + auto classList = api().classes(); + + std::sort(classList.begin(), classList.end(), + [](const AbstractMetaClassCPtr &a, const AbstractMetaClassCPtr &b) { + return a->typeEntry()->sbkIndex() < b->typeEntry()->sbkIndex(); + }); + + const auto typeIndexes = collectTypeIndexes(classList); + + macrosStream << "\n// Type indices\nenum [[deprecated]] : int {\n"; + for (const auto &ti : typeIndexes) + macrosStream << typeIndexUpper(ti); + macrosStream << "};\n"; + + macrosStream << "\n// Type indices\nenum : int {\n"; + for (const auto &ti : typeIndexes) + macrosStream << ti; + macrosStream << "};\n\n"; + + // FIXME: Remove backwards compatible variable in PySide 7. + macrosStream << "// This variable stores all Python types exported by this module.\n"; + macrosStream << "extern Shiboken::Module::TypeInitStruct *" << cppApiVariableName() << ";\n\n"; + macrosStream << "// This variable stores all Python types exported by this module "; + macrosStream << "in a backwards compatible way with identical indexing.\n"; + macrosStream << "[[deprecated]] extern PyTypeObject **" << cppApiVariableNameOld() << ";\n\n"; + macrosStream << "// This variable stores the Python module object exported by this module.\n"; + macrosStream << "extern PyObject *" << pythonModuleObjectName() << ";\n\n"; + macrosStream << "// This variable stores all type converters exported by this module.\n"; + macrosStream << "extern SbkConverter **" << convertersVariableName() << ";\n\n"; + + // TODO-CONVERTER ------------------------------------------------------------------------------ + // Using a counter would not do, a fix must be made to APIExtractor's getTypeIndex(). + const auto converterIndexes = collectConverterIndexes(); + macrosStream << "// Converter indices\nenum [[deprecated]] : int {\n"; + for (const auto &ci : converterIndexes) + macrosStream << typeIndexUpper(ci); + macrosStream << "};\n\n"; + + macrosStream << "// Converter indices\nenum : int {\n"; + for (const auto &ci : converterIndexes) + macrosStream << ci; + macrosStream << "};\n"; + + formatTypeDefEntries(macrosStream); + + // TODO-CONVERTER ------------------------------------------------------------------------------ + + macrosStream << "// Macros for type check\n"; + + TextStream typeFunctions(¶meters.typeFunctions, TextStream::Language::Cpp); + TextStream privateTypeFunctions(&privateParameters.typeFunctions, TextStream::Language::Cpp); + + for (const AbstractMetaEnum &cppEnum : api().globalEnums()) { + if (!cppEnum.isAnonymous()) { + const auto te = cppEnum.typeEntry(); + if (te->hasConfigCondition()) + parameters.conditionalIncludes[te->configCondition()].append(te->include()); + else + parameters.includes.insert(cppEnum.typeEntry()->include()); + writeSbkTypeFunction(typeFunctions, cppEnum); + } + } + + StringStream protEnumsSurrogates(TextStream::Language::Cpp); + for (const auto &metaClass : classList) { + const auto classType = metaClass->typeEntry(); + if (!shouldGenerate(classType)) + continue; + + // Includes + const bool isPrivate = classType->isPrivate(); + auto &par = isPrivate ? privateParameters : parameters; + const auto classInclude = classType->include(); + const bool hasConfigCondition = classType->hasConfigCondition(); + if (leanHeaders() && canForwardDeclare(metaClass)) + par.forwardDeclarations.append(metaClass); + else if (hasConfigCondition) + par.conditionalIncludes[classType->configCondition()].append(classInclude); + else + par.includes.insert(classInclude); + + auto &typeFunctionsStr = isPrivate ? privateTypeFunctions : typeFunctions; + + ConfigurableScope configScope(typeFunctionsStr, classType); + for (const AbstractMetaEnum &cppEnum : metaClass->enums()) { + if (cppEnum.isAnonymous() || cppEnum.isPrivate()) + continue; + if (const auto inc = cppEnum.typeEntry()->include(); inc != classInclude) + par.includes.insert(inc); + writeProtectedEnumSurrogate(protEnumsSurrogates, cppEnum); + writeSbkTypeFunction(typeFunctionsStr, cppEnum); + } + + if (!metaClass->isNamespace()) + writeSbkTypeFunction(typeFunctionsStr, metaClass); + } + + for (const auto &smp : api().instantiatedSmartPointers()) { + parameters.includes.insert(smp.type.typeEntry()->include()); + writeSbkTypeFunction(typeFunctions, smp.type); + } + + const QString moduleHeaderDir = outputDirectory() + u'/' + + subDirectoryForPackage(packageName()) + u'/'; + const QString moduleHeaderFileName(moduleHeaderDir + getModuleHeaderFileName()); + + QString includeShield(u"SBK_"_s + moduleName().toUpper() + u"_PYTHON_H"_s); + + FileOut file(moduleHeaderFileName); + TextStream &s = file.stream; + s.setLanguage(TextStream::Language::Cpp); + + // write license comment + s << licenseComment()<< "\n\n"; + + s << "#ifndef " << includeShield<< '\n'; + s << "#define " << includeShield<< "\n\n"; + if (!avoidProtectedHack()) { + s << "//workaround to access protected functions\n"; + s << "#define protected public\n\n"; + } + + s << "#include <sbkpython.h>\n"; + s << "#include <sbkmodule.h>\n"; + s << "#include <sbkconverter.h>\n"; + + QStringList requiredTargetImports = TypeDatabase::instance()->requiredTargetImports(); + if (!requiredTargetImports.isEmpty()) { + s << "// Module Includes\n"; + for (const QString &requiredModule : std::as_const(requiredTargetImports)) + s << "#include <" << getModuleHeaderFileName(requiredModule) << ">\n"; + s<< '\n'; + } + + s << "// Bound library includes\n"; + for (const Include &include : parameters.includes) + s << include; + s << parameters.conditionalIncludes; + + if (leanHeaders()) { + writeForwardDeclarations(s, parameters.forwardDeclarations); + } else { + if (!primitiveTypes().isEmpty()) { + s << "// Conversion Includes - Primitive Types\n"; + const auto &primitiveTypeList = primitiveTypes(); + for (const auto &ptype : primitiveTypeList) + s << ptype->include(); + s<< '\n'; + } + + if (!containerTypes().isEmpty()) { + s << "// Conversion Includes - Container Types\n"; + const ContainerTypeEntryCList &containerTypeList = containerTypes(); + for (const auto &ctype : containerTypeList) + s << ctype->include(); + s<< '\n'; + } + } + + s << macrosStream.toString() << '\n'; + + if (protEnumsSurrogates.size() > 0) { + s << "// Protected enum surrogates\n" + << protEnumsSurrogates.toString() << '\n'; + } + + writeTypeFunctions(s, parameters.typeFunctions); + + s << "#endif // " << includeShield << "\n\n"; + + file.done(); + + if (hasPrivateClasses()) + writePrivateHeader(moduleHeaderDir, includeShield, privateParameters); + + return true; +} + +void HeaderGenerator::writePrivateHeader(const QString &moduleHeaderDir, + const QString &publicIncludeShield, + const ModuleHeaderParameters ¶meters) +{ + // Write includes and type functions of private classes + + FileOut privateFile(moduleHeaderDir + getPrivateModuleHeaderFileName()); + TextStream &ps = privateFile.stream; + ps.setLanguage(TextStream::Language::Cpp); + QString privateIncludeShield = + publicIncludeShield.left(publicIncludeShield.size() - 2) + "_P_H"_L1; + + ps << licenseComment()<< "\n\n"; + + ps << "#ifndef " << privateIncludeShield << '\n'; + ps << "#define " << privateIncludeShield << "\n\n"; + + for (const Include &include : parameters.includes) + ps << include; + ps << parameters.conditionalIncludes; + ps << '\n'; + + if (leanHeaders()) + writeForwardDeclarations(ps, parameters.forwardDeclarations); + + writeTypeFunctions(ps, parameters.typeFunctions); + + ps << "#endif\n"; + privateFile.done(); +} + +void HeaderGenerator::writeTypeFunctions(TextStream &s, const QString &typeFunctions) +{ + if (typeFunctions.isEmpty()) + return; + + if (usePySideExtensions()) + s << "QT_WARNING_PUSH\nQT_WARNING_DISABLE_DEPRECATED\n"; + + s << "namespace Shiboken\n{\n\n" + << "// PyType functions, to get the PyObjectType for a type T\n" + << typeFunctions << '\n' + << "} // namespace Shiboken\n\n"; + + if (usePySideExtensions()) + s << "QT_WARNING_POP\n"; +} + +void HeaderGenerator::writeProtectedEnumSurrogate(TextStream &s, const AbstractMetaEnum &cppEnum) +{ + if (avoidProtectedHack() && cppEnum.isProtected()) + s << "enum " << protectedEnumSurrogateName(cppEnum) << " {};\n"; +} + +void HeaderGenerator::writeSbkTypeFunction(TextStream &s, const AbstractMetaEnum &cppEnum) +{ + const QString enumName = avoidProtectedHack() && cppEnum.isProtected() + ? protectedEnumSurrogateName(cppEnum) + : cppEnum.qualifiedCppName(); + const auto te = cppEnum.typeEntry(); + ConfigurableScope configScope(s, te); + s << "template<> inline PyTypeObject *SbkType< " << m_gsp << enumName << " >() "; + s << "{ return " << cpythonTypeNameExt(te) << "; }\n"; + + const auto flag = cppEnum.typeEntry()->flags(); + if (flag) { + s << "template<> inline PyTypeObject *SbkType< " << m_gsp << flag->name() << " >() " + << "{ return " << cpythonTypeNameExt(flag) << "; }\n"; + } +} + +void HeaderGenerator::writeSbkTypeFunction(TextStream &s, const AbstractMetaClassCPtr &cppClass) +{ + s << "template<> inline PyTypeObject *SbkType< " + << getFullTypeName(cppClass) << " >() " + << "{ return " << cpythonTypeNameExt(cppClass->typeEntry()) << "; }\n"; +} + +void HeaderGenerator::writeSbkTypeFunction(TextStream &s, const AbstractMetaType &metaType) +{ + s << "template<> inline PyTypeObject *SbkType< " + << m_gsp << metaType.cppSignature() << " >() " + << "{ return " << cpythonTypeNameExt(metaType) << "; }\n"; +} + +void HeaderGenerator::writeModuleCodeSnips(TextStream &s, const CodeSnipList &codeSnips, + TypeSystem::CodeSnipPosition position, + TypeSystem::Language language) const +{ + if (!codeSnips.isEmpty()) { + try { + writeCodeSnips(s, codeSnips, position, language); + } catch (const std::exception &e) { + throw Exception(msgSnippetError("module header of "_L1 + moduleName(), e.what())); + } + } +} diff --git a/sources/shiboken6/generator/shiboken/headergenerator.h b/sources/shiboken6/generator/shiboken/headergenerator.h new file mode 100644 index 000000000..03b98e743 --- /dev/null +++ b/sources/shiboken6/generator/shiboken/headergenerator.h @@ -0,0 +1,76 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef HEADERGENERATOR_H +#define HEADERGENERATOR_H + +#include "shibokengenerator.h" +#include "include.h" +#include "modifications_typedefs.h" + +#include <QtCore/QList> +#include <QtCore/QSet> + +struct IndexValue; +class AbstractMetaFunction; +struct ModuleHeaderParameters; + +/** + * The HeaderGenerator generate the declarations of C++ bindings classes. + */ +class HeaderGenerator : public ShibokenGenerator +{ +public: + const char *name() const override { return "Header generator"; } + + static const char *protectedHackDefine; + +protected: + QString fileNameForContext(const GeneratorContext &context) const override; + void generateClass(TextStream &s, const GeneratorContext &classContext) override; + bool finishGeneration() override; + +private: + using InheritedOverloadSet = QSet<AbstractMetaFunctionCPtr>; + using IndexValues = QList<IndexValue>; + + IndexValues collectTypeIndexes(const AbstractMetaClassCList &classList); + IndexValues collectConverterIndexes() const; + + static void writeCopyCtor(TextStream &s, const AbstractMetaClassCPtr &metaClass); + void writeFunction(TextStream &s, + const AbstractMetaFunctionCPtr &func, + InheritedOverloadSet *inheritedOverloads, + FunctionGeneration generation) const; + static void writeSbkTypeFunction(TextStream &s, const AbstractMetaEnum &cppEnum); + static void writeSbkTypeFunction(TextStream &s, const AbstractMetaClassCPtr &cppClass); + static void writeSbkTypeFunction(TextStream &s, const AbstractMetaType &metaType); + void collectTypeEntryTypeIndexes(const ApiExtractorResult &api, + const TypeEntryCPtr &typeEntry, + IndexValues *indexValues); + void collectClassTypeIndexes(const ApiExtractorResult &api, + const AbstractMetaClassCPtr &metaClass, + IndexValues *indexValues); + static void writeProtectedEnumSurrogate(TextStream &s, const AbstractMetaEnum &cppEnum); + void writeMemberFunctionWrapper(TextStream &s, + const AbstractMetaFunctionCPtr &func, + const QString &postfix = {}) const; + void writePrivateHeader(const QString &moduleHeaderDir, + const QString &publicIncludeShield, + const ModuleHeaderParameters ¶meters); + static void writeTypeFunctions(TextStream &s, const QString &typeFunctions); + void writeWrapperClassDeclaration(TextStream &s, + const QString &wrapperName, + const GeneratorContext &classContext) const; + void writeWrapperClass(TextStream &s, const QString &wrapperName, const GeneratorContext &classContext) const; + void writeInheritedWrapperClassDeclaration(TextStream &s, + const GeneratorContext &classContext) const; + void writeModuleCodeSnips(TextStream &s, const CodeSnipList &codeSnips, + TypeSystem::CodeSnipPosition position, + TypeSystem::Language language) const; + + AbstractMetaClassCList m_alternateTemplateIndexes; +}; + +#endif // HEADERGENERATOR_H + diff --git a/sources/shiboken6/generator/shiboken/overloaddata.cpp b/sources/shiboken6/generator/shiboken/overloaddata.cpp new file mode 100644 index 000000000..c28fcdc1a --- /dev/null +++ b/sources/shiboken6/generator/shiboken/overloaddata.cpp @@ -0,0 +1,1011 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include <abstractmetafunction.h> +#include <apiextractorresult.h> +#include <abstractmetalang.h> +#include <dotview.h> +#include <reporthandler.h> +#include <complextypeentry.h> +#include <containertypeentry.h> +#include <primitivetypeentry.h> +#include <graph.h> +#include "overloaddata.h" +#include "messages.h" +#include "ctypenames.h" +#include "pytypenames.h" +#include "textstream.h" +#include "exception.h" + +#include "qtcompat.h" + +#include <QtCore/QDir> +#include <QtCore/QFile> +#include <QtCore/QTemporaryFile> + +#include <algorithm> +#include <utility> + +using namespace Qt::StringLiterals; + +static QString getTypeName(const AbstractMetaType &type) +{ + TypeEntryCPtr typeEntry = type.typeEntry(); + if (typeEntry->isPrimitive()) + typeEntry = basicReferencedTypeEntry(typeEntry); + QString typeName = typeEntry->name(); + if (typeEntry->isContainer()) { + QStringList types; + for (const auto &cType : type.instantiations()) { + TypeEntryCPtr typeEntry = cType.typeEntry(); + if (typeEntry->isPrimitive()) + typeEntry = basicReferencedTypeEntry(typeEntry); + types << typeEntry->name(); + } + typeName += u'<' + types.join(u',') + u" >"_s; + } + return typeName; +} + +static bool typesAreEqual(const AbstractMetaType &typeA, const AbstractMetaType &typeB) +{ + if (typeA.typeEntry() == typeB.typeEntry()) { + if (typeA.isContainer() || typeA.isSmartPointer()) { + if (typeA.instantiations().size() != typeB.instantiations().size()) + return false; + + for (qsizetype i = 0; i < typeA.instantiations().size(); ++i) { + if (!typesAreEqual(typeA.instantiations().at(i), typeB.instantiations().at(i))) + return false; + } + return true; + } + + return !(typeA.isCString() ^ typeB.isCString()); + } + return false; +} + +/** + * Helper function that returns the name of a container get from containerType argument and + * an instantiation taken either from an implicit conversion expressed by the function argument, + * or from the string argument implicitConvTypeName. + */ +static QString getImplicitConversionTypeName(const AbstractMetaType &containerType, + const AbstractMetaType &instantiation, + const AbstractMetaFunctionCPtr &function, + const QString &implicitConvTypeName = QString()) +{ + QString impConv; + if (!implicitConvTypeName.isEmpty()) + impConv = implicitConvTypeName; + else if (function->isConversionOperator()) + impConv = function->ownerClass()->typeEntry()->name(); + else + impConv = getTypeName(function->arguments().constFirst().type()); + + QStringList types; + for (const auto &otherType : containerType.instantiations()) + types << (otherType == instantiation ? impConv : getTypeName(otherType)); + + return containerType.typeEntry()->qualifiedCppName() + u'<' + + types.join(u", "_s) + u" >"_s; +} + +static inline int overloadNumber(const OverloadDataNodePtr &o) +{ + return o->referenceFunction()->overloadNumber(); +} + +static bool sortByOverloadNumberModification(OverloadDataList &list) +{ + if (std::all_of(list.cbegin(), list.cend(), + [](const OverloadDataNodePtr &o) { return overloadNumber(o) == TypeSystem::OverloadNumberDefault; })) { + return false; + } + std::stable_sort(list.begin(), list.end(), + [] (const OverloadDataNodePtr &o1, const OverloadDataNodePtr &o2) { + return overloadNumber(o1) < overloadNumber(o2); + }); + return true; +} + +using OverloadGraph = Graph<QString>; + +/** + * Topologically sort the overloads by implicit convertion order + * + * This avoids using an implicit conversion if there's an explicit + * overload for the convertible type. So, if there's an implicit convert + * like TargetType(ConvertibleType foo) and both are in the overload list, + * ConvertibleType is checked before TargetType. + * + * Side effects: Modifies m_nextOverloadData + */ +void OverloadDataRootNode::sortNextOverloads(const ApiExtractorResult &api) +{ + QHash<QString, OverloadDataList> typeToOverloads; + using Edge = std::pair<QString, QString>; + + bool checkPyObject = false; + bool checkPySequence = false; + bool checkQString = false; + bool checkQVariant = false; + bool checkPyBuffer = false; + + // Primitive types that are not int, long, short, + // char and their respective unsigned counterparts. + static const QStringList nonIntegerPrimitives{floatT, doubleT, boolT}; + + // Signed integer primitive types. + static const QStringList signedIntegerPrimitives{intT, shortT, longT, longLongT}; + + // sort the children overloads + for (const auto &ov : std::as_const(m_children)) + ov->sortNextOverloads(api); + + if (m_children.size() <= 1 || sortByOverloadNumberModification(m_children)) + return; + + // Populates the OverloadSortData object containing map and reverseMap, to map type names to ids, + // these ids will be used by the topological sort algorithm, because is easier and faster to work + // with graph sorting using integers. + + OverloadGraph graph; + for (const auto &ov : std::as_const(m_children)) { + const QString typeName = getTypeName(ov->modifiedArgType()); + auto it = typeToOverloads.find(typeName); + if (it == typeToOverloads.end()) { + typeToOverloads.insert(typeName, {ov}); + graph.addNode(typeName); + } else { + it.value().append(ov); + } + + if (!checkPyObject && typeName == cPyObjectT) + checkPyObject = true; + else if (!checkPySequence && typeName == cPySequenceT) + checkPySequence = true; + else if (!checkPyBuffer && typeName == cPyBufferT) + checkPyBuffer = true; + else if (!checkQVariant && typeName == qVariantT) + checkQVariant = true; + else if (!checkQString && typeName == qStringT) + checkQString = true; + + for (const auto &instantiation : ov->argType().instantiations()) { + // Add dependencies for type instantiation of container. + graph.addNode(getTypeName(instantiation)); + + // Build dependency for implicit conversion types instantiations for base container. + // For example, considering signatures "method(list<PointF>)" and "method(list<Point>)", + // and being PointF implicitly convertible from Point, an list<T> instantiation with T + // as Point must come before the PointF instantiation, or else list<Point> will never + // be called. In the case of primitive types, list<double> must come before list<int>. + if (instantiation.isPrimitive() && (signedIntegerPrimitives.contains(instantiation.name()))) { + for (const QString &primitive : std::as_const(nonIntegerPrimitives)) + graph.addNode(getImplicitConversionTypeName(ov->argType(), instantiation, nullptr, primitive)); + } else { + const auto &funcs = api.implicitConversions(instantiation); + for (const auto &function : funcs) + graph.addNode(getImplicitConversionTypeName(ov->argType(), instantiation, function)); + } + } + } + + + // Create the graph of type dependencies based on implicit conversions. + // All C++ primitive types, add any forgotten type AT THE END OF THIS LIST! + static const QStringList primitiveTypes{intT, unsignedIntT, longT, unsignedLongT, + shortT, unsignedShortT, boolT, unsignedCharT, charT, floatT, + doubleT, constCharPtrT}; + + QStringList foundPrimitiveTypeIds; + for (const auto &p : primitiveTypes) { + if (graph.hasNode(p)) + foundPrimitiveTypeIds.append(p); + } + + if (checkPySequence && checkPyObject) + graph.addEdge(cPySequenceT, cPyObjectT); + + QStringList classesWithIntegerImplicitConversion; + + AbstractMetaFunctionCList involvedConversions; + + for (const auto &ov : std::as_const(m_children)) { + const AbstractMetaType &targetType = ov->argType(); + const QString targetTypeEntryName = getTypeName(ov->modifiedArgType()); + + // Process implicit conversions + const auto &functions = api.implicitConversions(targetType); + for (const auto &function : functions) { + QString convertibleType; + if (function->isConversionOperator()) + convertibleType = function->ownerClass()->typeEntry()->name(); + else + convertibleType = getTypeName(function->arguments().constFirst().type()); + + if (convertibleType == intT || convertibleType == unsignedIntT) + classesWithIntegerImplicitConversion << targetTypeEntryName; + + if (!graph.hasNode(convertibleType)) + continue; + + // If a reverse pair already exists, remove it. Probably due to the + // container check (This happened to QVariant and QHash) + graph.removeEdge(targetTypeEntryName, convertibleType); + graph.addEdge(convertibleType, targetTypeEntryName); + involvedConversions.append(function); + } + + // Process inheritance relationships + if (targetType.isValue() || targetType.isObject()) { + const auto te = targetType.typeEntry(); + auto metaClass = AbstractMetaClass::findClass(api.classes(), te); + if (!metaClass) + throw Exception(msgArgumentClassNotFound(m_overloads.constFirst(), te)); + const auto &ancestors = metaClass->allTypeSystemAncestors(); + for (const auto &ancestor : ancestors) { + QString ancestorTypeName = ancestor->typeEntry()->name(); + if (!graph.hasNode(ancestorTypeName)) + continue; + graph.removeEdge(ancestorTypeName, targetTypeEntryName); + graph.addEdge(targetTypeEntryName, ancestorTypeName); + } + } + + // Process template instantiations + for (const auto &instantiation : targetType.instantiations()) { + const QString convertible = getTypeName(instantiation); + if (graph.hasNode(convertible)) { + if (!graph.containsEdge(targetTypeEntryName, convertible)) // Avoid cyclic dependency. + graph.addEdge(convertible, targetTypeEntryName); + + if (instantiation.isPrimitive() && (signedIntegerPrimitives.contains(instantiation.name()))) { + for (const QString &primitive : std::as_const(nonIntegerPrimitives)) { + QString convertibleTypeName = + getImplicitConversionTypeName(ov->argType(), instantiation, nullptr, primitive); + // Avoid cyclic dependency. + if (!graph.containsEdge(targetTypeEntryName, convertibleTypeName)) + graph.addEdge(convertibleTypeName, targetTypeEntryName); + } + + } else { + const auto &funcs = api.implicitConversions(instantiation); + for (const auto &function : funcs) { + QString convertibleTypeName = + getImplicitConversionTypeName(ov->argType(), instantiation, function); + // Avoid cyclic dependency. + if (!graph.containsEdge(targetTypeEntryName, convertibleTypeName)) { + graph.addEdge(convertibleTypeName, targetTypeEntryName); + involvedConversions.append(function); + } + } + } + } + } + + + if ((checkPySequence || checkPyObject || checkPyBuffer) + && !targetTypeEntryName.contains(cPyObjectT) + && !targetTypeEntryName.contains(cPyBufferT) + && !targetTypeEntryName.contains(cPySequenceT)) { + if (checkPySequence) { + // PySequence will be checked after all more specific types, but before PyObject. + graph.addEdge(targetTypeEntryName, cPySequenceT); + } else if (checkPyBuffer) { + // PySequence will be checked after all more specific types, but before PyObject. + graph.addEdge(targetTypeEntryName, cPyBufferT); + } else { + // Add dependency on PyObject, so its check is the last one (too generic). + graph.addEdge(targetTypeEntryName, cPyObjectT); + } + } else if (checkQVariant && targetTypeEntryName != qVariantT) { + if (!graph.containsEdge(qVariantT, targetTypeEntryName)) // Avoid cyclic dependency. + graph.addEdge(targetTypeEntryName, qVariantT); + } else if (checkQString && ov->argType().isPointer() + && targetTypeEntryName != qStringT + && targetTypeEntryName != qByteArrayT + && (!checkPyObject || targetTypeEntryName != cPyObjectT)) { + if (!graph.containsEdge(qStringT, targetTypeEntryName)) // Avoid cyclic dependency. + graph.addEdge(targetTypeEntryName, qStringT); + } + + if (targetType.isEnum()) { + // Enum values must precede primitive types. + for (const auto &id : foundPrimitiveTypeIds) + graph.addEdge(targetTypeEntryName, id); + } + } + + // QByteArray args need to be checked after QString args + if (graph.hasNode(qStringT) && graph.hasNode(qByteArrayT)) + graph.addEdge(qStringT, qByteArrayT); + + static const Edge rangeOrder[] = + {{doubleT, floatT}, + {longLongT, longT}, {longLongT, intT}, {intT, shortT}, + {unsignedLongLongT, unsignedLongT}, {unsignedLongLongT, unsignedT}, + {unsignedLongLongT, unsignedIntT}, {unsignedT, unsignedShortT} + }; + for (const auto &r : rangeOrder) { + if (graph.hasNode(r.first) && graph.hasNode(r.second)) + graph.addEdge(r.first, r.second); + } + + for (const auto &ov : std::as_const(m_children)) { + const AbstractMetaType &targetType = ov->argType(); + if (!targetType.isEnum()) + continue; + + QString targetTypeEntryName = getTypeName(targetType); + // Enum values must precede types implicitly convertible from "int" or "unsigned int". + for (const QString &implicitFromInt : std::as_const(classesWithIntegerImplicitConversion)) + graph.addEdge(targetTypeEntryName, implicitFromInt); + } + + + // Special case for double(int i) (not tracked by m_generator->implicitConversions + for (const QString &signedIntegerName : std::as_const(signedIntegerPrimitives)) { + if (graph.hasNode(signedIntegerName)) { + for (const QString &nonIntegerName : std::as_const(nonIntegerPrimitives)) { + if (graph.hasNode(nonIntegerName)) + graph.addEdge(nonIntegerName, signedIntegerName); + } + } + } + + // sort the overloads topologically based on the dependency graph. + const auto unmappedResult = graph.topologicalSort(); + if (!unmappedResult.isValid()) { + QString funcName = referenceFunction()->name(); + if (auto owner = referenceFunction()->ownerClass()) + funcName.prepend(owner->name() + u'.'); + + // Dump overload graph + QString graphName = QDir::tempPath() + u'/' + funcName + u".dot"_s; + graph.dumpDot(graphName, [] (const QString &n) { return n; }); + AbstractMetaFunctionCList cyclic; + for (const auto &typeName : unmappedResult.cyclic) { + const auto oit = typeToOverloads.constFind(typeName); + if (oit != typeToOverloads.cend()) + cyclic.append(oit.value().constFirst()->referenceFunction()); + } + qCWarning(lcShiboken, "%s", qPrintable(msgCyclicDependency(funcName, graphName, cyclic, involvedConversions))); + } + + m_children.clear(); + for (const auto &typeName : unmappedResult.result) { + const auto oit = typeToOverloads.constFind(typeName); + if (oit != typeToOverloads.cend()) { + std::copy(oit.value().crbegin(), oit.value().crend(), + std::back_inserter(m_children)); + } + } +} + +// Determine the minimum (first default argument)/maximum arguments (size) +// of a function (taking into account the removed arguments). +static std::pair<int, int> getMinMaxArgs(const AbstractMetaFunctionCPtr &func) +{ + int defaultValueIndex = -1; + const auto &arguments = func->arguments(); + int argIndex = 0; + for (const auto &arg : arguments) { + if (!arg.isModifiedRemoved()) { + if (defaultValueIndex < 0 && arg.hasDefaultValueExpression()) + defaultValueIndex = argIndex; + ++argIndex; + } + } + const int maxArgs = argIndex; + const int minArgs = defaultValueIndex >= 0 ? defaultValueIndex : maxArgs; + return {minArgs, maxArgs}; +} + +const OverloadDataRootNode *OverloadDataNode::parent() const +{ + return m_parent; +} + +/** + * Root constructor for OverloadData + * + * This constructor receives the list of overloads for a given function and iterates generating + * the graph of OverloadData instances. Each OverloadDataNode instance references an argument/type + * combination. + * + * Example: + * addStuff(double, PyObject *) + * addStuff(double, int) + * + * Given these two overloads, there will be the following graph: + * + * addStuff - double - PyObject * + * \- int + * + */ +OverloadData::OverloadData(const AbstractMetaFunctionCList &overloads, + const ApiExtractorResult &api) : + OverloadDataRootNode(overloads) +{ + for (const auto &func : overloads) { + const auto minMaxArgs = getMinMaxArgs(func); + if (minMaxArgs.first < m_minArgs) + m_minArgs = minMaxArgs.first; + if (minMaxArgs.second > m_maxArgs) + m_maxArgs = minMaxArgs.second; + OverloadDataRootNode *currentOverloadData = this; + const AbstractMetaArgumentList &arguments = func->arguments(); + for (const AbstractMetaArgument &arg : arguments) { + if (!arg.isModifiedRemoved()) + currentOverloadData = currentOverloadData->addOverloadDataNode(func, arg); + } + } + + // Sort the overload possibilities so that the overload decisor code goes for the most + // important cases first, based on the topological order of the implicit conversions + sortNextOverloads(api); +} + +OverloadDataNode::OverloadDataNode(const AbstractMetaFunctionCPtr &func, + OverloadDataRootNode *parent, + const AbstractMetaArgument &argument, + int argPos, + const QString argTypeReplaced) : + m_argument(argument), + m_argTypeReplaced(argTypeReplaced), + m_parent(parent), + m_argPos(argPos) +{ + if (func) + this->addOverload(func); +} + +void OverloadDataNode::addOverload(const AbstractMetaFunctionCPtr &func) +{ + m_overloads.append(func); +} + +OverloadDataNode *OverloadDataRootNode::addOverloadDataNode(const AbstractMetaFunctionCPtr &func, + const AbstractMetaArgument &arg) +{ + OverloadDataNodePtr overloadData; + if (!func->isOperatorOverload()) { + for (const auto &tmp : std::as_const(m_children)) { + // TODO: 'const char *', 'char *' and 'char' will have the same TypeEntry? + + // If an argument have a type replacement, then we should create a new overloaddata + // for it, unless the next argument also have a identical type replacement. + if (typesAreEqual(tmp->modifiedArgType(), arg.modifiedType())) { + tmp->addOverload(func); + overloadData = tmp; + } + } + } + + if (!overloadData) { + const int argpos = argPos() + 1; + overloadData.reset(new OverloadDataNode(func, this, arg, argpos)); + m_children.append(overloadData); + } + + return overloadData.get(); +} + +bool OverloadData::hasNonVoidReturnType() const +{ + for (const auto &func : m_overloads) { + if (func->isTypeModified()) { + if (func->modifiedTypeName() != u"void") + return true; + } else { + if (!func->argumentRemoved(0) && !func->type().isVoid()) + return true; + } + } + return false; +} + +bool OverloadData::hasVarargs() const +{ + for (const auto &func : m_overloads) { + AbstractMetaArgumentList args = func->arguments(); + if (args.size() > 1 && args.constLast().type().isVarargs()) + return true; + } + return false; +} + +bool OverloadData::hasStaticFunction(const AbstractMetaFunctionCList &overloads) +{ + for (const auto &func : overloads) { + if (func->isStatic()) + return true; + } + return false; +} + +bool OverloadData::hasStaticFunction() const +{ + for (const auto &func : m_overloads) { + if (func->isStatic()) + return true; + } + return false; +} + +bool OverloadData::hasClassMethod(const AbstractMetaFunctionCList &overloads) +{ + for (const auto &func : overloads) { + if (func->isClassMethod()) + return true; + } + return false; +} + +bool OverloadData::hasClassMethod() const +{ + for (const auto &func : m_overloads) { + if (func->isClassMethod()) + return true; + } + return false; +} + +bool OverloadData::hasInstanceFunction(const AbstractMetaFunctionCList &overloads) +{ + for (const auto &func : overloads) { + if (!func->isStatic()) + return true; + } + return false; +} + +bool OverloadData::hasInstanceFunction() const +{ + for (const auto &func : m_overloads) { + if (!func->isStatic()) + return true; + } + return false; +} + +bool OverloadData::hasStaticAndInstanceFunctions(const AbstractMetaFunctionCList &overloads) +{ + return OverloadData::hasStaticFunction(overloads) && OverloadData::hasInstanceFunction(overloads); +} + +bool OverloadData::hasStaticAndInstanceFunctions() const +{ + return OverloadData::hasStaticFunction() && OverloadData::hasInstanceFunction(); +} + +OverloadDataRootNode::OverloadDataRootNode(const AbstractMetaFunctionCList &o) : + m_overloads(o) +{ +} + +OverloadDataRootNode::~OverloadDataRootNode() = default; + +AbstractMetaFunctionCPtr OverloadDataRootNode::referenceFunction() const +{ + return m_overloads.constFirst(); +} + +const AbstractMetaArgument *OverloadDataNode::overloadArgument(const AbstractMetaFunctionCPtr &func) const +{ + if (isRoot() || !m_overloads.contains(func)) + return nullptr; + + int argPos = 0; + int removed = 0; + for (int i = 0; argPos <= m_argPos; i++) { + if (func->arguments().at(i).isModifiedRemoved()) + removed++; + else + argPos++; + } + + return &func->arguments().at(m_argPos + removed); +} + +bool OverloadDataRootNode::nextArgumentHasDefaultValue() const +{ + for (const auto &overloadData : m_children) { + if (overloadData->getFunctionWithDefaultValue()) + return true; + } + return false; +} + +static const OverloadDataRootNode *_findNextArgWithDefault(const OverloadDataRootNode *overloadData) +{ + if (overloadData->getFunctionWithDefaultValue()) + return overloadData; + + const OverloadDataRootNode *result = nullptr; + const OverloadDataList &data = overloadData->children(); + for (const auto &odata : data) { + const auto *tmp = _findNextArgWithDefault(odata.get()); + if (!result || (tmp && result->argPos() > tmp->argPos())) + result = tmp; + } + return result; +} + +const OverloadDataRootNode *OverloadDataRootNode::findNextArgWithDefault() const +{ + return _findNextArgWithDefault(this); +} + +bool OverloadDataRootNode::isFinalOccurrence(const AbstractMetaFunctionCPtr &func) const +{ + for (const auto &pd : m_children) { + if (pd->overloads().contains(func)) + return false; + } + return true; +} + +AbstractMetaFunctionCPtr OverloadDataRootNode::getFunctionWithDefaultValue() const +{ + const qsizetype argpos = argPos(); + for (const auto &func : m_overloads) { + qsizetype removedArgs = 0; + for (qsizetype i = 0; i <= argpos + removedArgs; i++) { + if (func->arguments().at(i).isModifiedRemoved()) + removedArgs++; + } + if (func->arguments().at(argpos + removedArgs).hasDefaultValueExpression()) + return func; + } + return {}; +} + +QList<int> OverloadData::invalidArgumentLengths() const +{ + QSet<int> validArgLengths; + + for (const auto &func : m_overloads) { + const AbstractMetaArgumentList args = func->arguments(); + int offset = 0; + for (qsizetype i = 0; i < args.size(); ++i) { + if (func->arguments().at(i).isModifiedRemoved()) { + offset++; + } else { + if (args.at(i).hasDefaultValueExpression()) + validArgLengths << i-offset; + } + } + validArgLengths << args.size() - offset; + } + + QList<int> invalidArgLengths; + for (int i = m_minArgs + 1; i < m_maxArgs; i++) { + if (!validArgLengths.contains(i)) + invalidArgLengths.append(i); + } + + return invalidArgLengths; +} + +int OverloadData::numberOfRemovedArguments(const AbstractMetaFunctionCPtr &func) +{ + return std::count_if(func->arguments().cbegin(), func->arguments().cend(), + [](const AbstractMetaArgument &a) { return a.isModifiedRemoved(); }); +} + +int OverloadData::numberOfRemovedArguments(const AbstractMetaFunctionCPtr &func, int finalArgPos) +{ + Q_ASSERT(finalArgPos >= 0); + int removed = 0; + const auto size = func->arguments().size(); + for (qsizetype i = 0; i < qMin(size, qsizetype(finalArgPos + removed)); ++i) { + if (func->arguments().at(i).isModifiedRemoved()) + ++removed; + } + return removed; +} + +void OverloadData::dumpGraph(const QString &filename) const +{ + QFile file(filename); + if (file.open(QFile::WriteOnly)) { + QTextStream s(&file); + dumpRootGraph(s, m_minArgs, m_maxArgs); + } +} + +QString OverloadData::dumpGraph() const +{ + QString result; + QTextStream s(&result); + dumpRootGraph(s, m_minArgs, m_maxArgs); + return result; +} + +bool OverloadData::showGraph() const +{ + return showDotGraph(referenceFunction()->name(), dumpGraph()); +} + +static inline QString toHtml(QString s) +{ + s.replace(u'<', u"<"_s); + s.replace(u'>', u">"_s); + s.replace(u'&', u"&"_s); + return s; +} + +void OverloadDataRootNode::dumpRootGraph(QTextStream &s, int minArgs, int maxArgs) const +{ + const auto rfunc = referenceFunction(); + s << "digraph OverloadedFunction {\n"; + s << " graph [fontsize=12 fontname=freemono labelloc=t splines=true overlap=false rankdir=LR];\n"; + + // Shows all function signatures + s << "legend [fontsize=9 fontname=freemono shape=rect label=\""; + for (const auto &func : m_overloads) { + s << "f" << functionNumber(func) << " : " + << toHtml(func->type().cppSignature()) + << ' ' << toHtml(func->minimalSignature()) << "\\l"; + } + s << "\"];\n"; + + // Function box title + s << " \"" << rfunc->name() << "\" [shape=plaintext style=\"filled,bold\" margin=0 fontname=freemono fillcolor=white penwidth=1 "; + s << "label=<<table border=\"0\" cellborder=\"0\" cellpadding=\"3\" bgcolor=\"white\">"; + s << "<tr><td bgcolor=\"black\" align=\"center\" cellpadding=\"6\" colspan=\"2\"><font color=\"white\">"; + if (rfunc->ownerClass()) + s << rfunc->ownerClass()->name() << "::"; + s << toHtml(rfunc->name()) << "</font>"; + if (rfunc->isVirtual()) { + s << "<br/><font color=\"white\" point-size=\"10\"><<"; + if (rfunc->isAbstract()) + s << "pure "; + s << "virtual>></font>"; + } + s << "</td></tr>"; + + // Function return type + s << "<tr><td bgcolor=\"gray\" align=\"right\">original type</td><td bgcolor=\"gray\" align=\"left\">" + << toHtml(rfunc->type().cppSignature()) + << "</td></tr>"; + + // Shows type changes for all function signatures + for (const auto &func : m_overloads) { + if (!func->isTypeModified()) + continue; + s << "<tr><td bgcolor=\"gray\" align=\"right\">f" << functionNumber(func); + s << "-type</td><td bgcolor=\"gray\" align=\"left\">"; + s << toHtml(func->modifiedTypeName()) << "</td></tr>"; + } + + // Minimum and maximum number of arguments + s << "<tr><td bgcolor=\"gray\" align=\"right\">minArgs</td><td bgcolor=\"gray\" align=\"left\">"; + s << minArgs << "</td></tr>"; + s << "<tr><td bgcolor=\"gray\" align=\"right\">maxArgs</td><td bgcolor=\"gray\" align=\"left\">"; + s << maxArgs << "</td></tr>"; + + if (rfunc->ownerClass()) { + if (rfunc->implementingClass() != rfunc->ownerClass()) + s << "<tr><td align=\"right\">implementor</td><td align=\"left\">" << rfunc->implementingClass()->name() << "</td></tr>"; + if (rfunc->declaringClass() != rfunc->ownerClass() && rfunc->declaringClass() != rfunc->implementingClass()) + s << "<tr><td align=\"right\">declarator</td><td align=\"left\">" << rfunc->declaringClass()->name() << "</td></tr>"; + } + + // Overloads for the signature to present point + s << "<tr><td bgcolor=\"gray\" align=\"right\">overloads</td><td bgcolor=\"gray\" align=\"left\">"; + for (const auto &func : m_overloads) + s << 'f' << functionNumber(func) << ' '; + s << "</td></tr>"; + + s << "</table>> ];\n"; + + for (const auto &pd : m_children) { + s << " \"" << rfunc->name() << "\" -> "; + pd->dumpNodeGraph(s); + } + + s << "}\n"; +} + +void OverloadDataNode::dumpNodeGraph(QTextStream &s) const +{ + QString argId = u"arg_"_s + QString::number(quintptr(this)); + s << argId << ";\n"; + + s << " \"" << argId << "\" [shape=\"plaintext\" style=\"filled,bold\" margin=\"0\" fontname=\"freemono\" fillcolor=\"white\" penwidth=1 "; + s << "label=<<table border=\"0\" cellborder=\"0\" cellpadding=\"3\" bgcolor=\"white\">"; + + // Argument box title + s << "<tr><td bgcolor=\"black\" align=\"left\" cellpadding=\"2\" colspan=\"2\">"; + s << "<font color=\"white\" point-size=\"11\">arg #" << argPos() << "</font></td></tr>"; + + // Argument type information + const QString type = modifiedArgType().cppSignature(); + s << "<tr><td bgcolor=\"gray\" align=\"right\">type</td><td bgcolor=\"gray\" align=\"left\">"; + s << toHtml(type) << "</td></tr>"; + if (isTypeModified()) { + s << "<tr><td bgcolor=\"gray\" align=\"right\">orig. type</td><td bgcolor=\"gray\" align=\"left\">"; + s << toHtml(argType().cppSignature()) << "</td></tr>"; + } + + const OverloadDataRootNode *root = this; + while (!root->isRoot()) + root = root->parent(); + + // Overloads for the signature to present point + s << "<tr><td bgcolor=\"gray\" align=\"right\">overloads</td><td bgcolor=\"gray\" align=\"left\">"; + for (const auto &func : m_overloads) + s << 'f' << root->functionNumber(func) << ' '; + s << "</td></tr>"; + + // Show default values (original and modified) for various functions + for (const auto &func : m_overloads) { + const AbstractMetaArgument *arg = overloadArgument(func); + if (!arg) + continue; + const int n = root->functionNumber(func); + QString argDefault = arg->defaultValueExpression(); + if (!argDefault.isEmpty() || + argDefault != arg->originalDefaultValueExpression()) { + s << "<tr><td bgcolor=\"gray\" align=\"right\">f" << n; + s << "-default</td><td bgcolor=\"gray\" align=\"left\">"; + s << argDefault << "</td></tr>"; + } + if (argDefault != arg->originalDefaultValueExpression()) { + s << "<tr><td bgcolor=\"gray\" align=\"right\">f" << n; + s << "-orig-default</td><td bgcolor=\"gray\" align=\"left\">"; + s << arg->originalDefaultValueExpression() << "</td></tr>"; + } + } + + s << "</table>>];\n"; + + for (const auto &pd : m_children) { + s << " " << argId << " -> "; + pd->dumpNodeGraph(s); + } +} + +int OverloadDataRootNode::functionNumber(const AbstractMetaFunctionCPtr &func) const +{ + return m_overloads.indexOf(func); +} + +bool OverloadData::pythonFunctionWrapperUsesListOfArguments() const +{ + auto referenceFunction = m_overloads.constFirst(); + if (referenceFunction->isCallOperator()) + return true; + if (referenceFunction->isOperatorOverload()) + return false; + const int maxArgs = this->maxArgs(); + const int minArgs = this->minArgs(); + return (minArgs != maxArgs) + || (maxArgs > 1) + || referenceFunction->isConstructor() + || hasArgumentWithDefaultValue(); +} + +bool OverloadData::hasArgumentWithDefaultValue() const +{ + if (maxArgs() == 0) + return false; + for (const auto &func : m_overloads) { + if (hasArgumentWithDefaultValue(func)) + return true; + } + return false; +} + +bool OverloadData::hasArgumentWithDefaultValue(const AbstractMetaFunctionCPtr &func) +{ + const AbstractMetaArgumentList &arguments = func->arguments(); + for (const AbstractMetaArgument &arg : arguments) { + if (!arg.isModifiedRemoved() && arg.hasDefaultValueExpression()) + return true; + } + return false; +} + +AbstractMetaArgumentList OverloadData::getArgumentsWithDefaultValues(const AbstractMetaFunctionCPtr &func) +{ + AbstractMetaArgumentList args; + const AbstractMetaArgumentList &arguments = func->arguments(); + for (const AbstractMetaArgument &arg : arguments) { + if (!arg.hasDefaultValueExpression() + || arg.isModifiedRemoved()) + continue; + args << arg; + } + return args; +} + +#ifndef QT_NO_DEBUG_STREAM + +void OverloadDataRootNode::formatReferenceFunction(QDebug &d) const +{ + auto refFunc = referenceFunction(); + d << '"'; + if (auto owner = refFunc->ownerClass()) + d << owner->qualifiedCppName() << "::"; + d << refFunc->minimalSignature() << '"'; + if (m_overloads.constFirst()->isReverseOperator()) + d << " [reverseop]"; +} + +void OverloadDataRootNode::formatOverloads(QDebug &d) const +{ + const qsizetype count = m_overloads.size(); + d << ", overloads[" << count << ']'; + if (count < 2) + return; + d << "=("; + for (qsizetype i = 0; i < count; ++i) { + if (i) + d << '\n'; + d << m_overloads.at(i)->signature(); + } + d << ')'; +} + +void OverloadDataRootNode::formatNextOverloadData(QDebug &d) const +{ + const qsizetype count = m_children.size(); + d << ", next[" << count << ']'; + if (d.verbosity() >= 3) { + d << "=("; + for (qsizetype i = 0; i < count; ++i) { + if (i) + d << '\n'; + m_children.at(i)->formatDebug(d); + } + d << ')'; + } +} + +void OverloadDataRootNode::formatDebug(QDebug &d) const +{ + formatReferenceFunction(d); + formatOverloads(d); + formatNextOverloadData(d); +} + +void OverloadDataNode::formatDebug(QDebug &d) const +{ + d << "OverloadDataNode("; + formatReferenceFunction(d); + d << ", argPos=" << m_argPos; + if (m_argument.argumentIndex() != m_argPos) + d << ", argIndex=" << m_argument.argumentIndex(); + d << ", argType=\"" << m_argument.type().cppSignature() << '"'; + if (isTypeModified()) + d << ", modifiedArgType=\"" << modifiedArgType().cppSignature() << '"'; + formatOverloads(d); + formatNextOverloadData(d); + d << ')'; +} + +void OverloadData::formatDebug(QDebug &d) const +{ + d << "OverloadData("; + formatReferenceFunction(d); + d << ", minArgs=" << m_minArgs << ", maxArgs=" << m_maxArgs; + formatOverloads(d); + formatNextOverloadData(d); + d << ')'; +} + +QDebug operator<<(QDebug d, const OverloadData &od) +{ + QDebugStateSaver saver(d); + d.noquote(); + d.nospace(); + od.formatDebug(d); + return d; +} +#endif // !QT_NO_DEBUG_STREAM diff --git a/sources/shiboken6/generator/shiboken/overloaddata.h b/sources/shiboken6/generator/shiboken/overloaddata.h new file mode 100644 index 000000000..875a5a8b5 --- /dev/null +++ b/sources/shiboken6/generator/shiboken/overloaddata.h @@ -0,0 +1,183 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef OVERLOADDATA_H +#define OVERLOADDATA_H + +#include <apiextractorresult.h> +#include <abstractmetaargument.h> + +#include <QtCore/QBitArray> +#include <QtCore/QList> + +#include <memory> + +QT_FORWARD_DECLARE_CLASS(QDebug) +QT_FORWARD_DECLARE_CLASS(QTextStream) + +class OverloadDataNode; +using OverloadDataNodePtr = std::shared_ptr<OverloadDataNode>; +using OverloadDataList = QList<OverloadDataNodePtr>; + +/// The root node of OverloadData. It contains all functions +class OverloadDataRootNode +{ +public: + virtual ~OverloadDataRootNode(); + + OverloadDataRootNode(const OverloadDataRootNode &) = delete; + OverloadDataRootNode &operator=(const OverloadDataRootNode &) = delete; + OverloadDataRootNode(OverloadDataRootNode &&) = delete; + OverloadDataRootNode &operator=(OverloadDataRootNode &&) = delete; + + virtual int argPos() const { return -1; } + virtual const OverloadDataRootNode *parent() const { return nullptr; } + + bool isRoot() const { return parent() == nullptr; } + + AbstractMetaFunctionCPtr referenceFunction() const; + + const AbstractMetaFunctionCList &overloads() const { return m_overloads; } + const OverloadDataList &children() const { return m_children; } + + bool nextArgumentHasDefaultValue() const; + + /// Returns the function that has a default value at the current OverloadData argument position, otherwise returns null. + AbstractMetaFunctionCPtr getFunctionWithDefaultValue() const; + + /// Returns the nearest occurrence, including this instance, of an argument with a default value. + const OverloadDataRootNode *findNextArgWithDefault() const; + bool isFinalOccurrence(const AbstractMetaFunctionCPtr &func) const; + + int functionNumber(const AbstractMetaFunctionCPtr &func) const; + +#ifndef QT_NO_DEBUG_STREAM + virtual void formatDebug(QDebug &d) const; +#endif + + OverloadDataNode *addOverloadDataNode(const AbstractMetaFunctionCPtr &func, + const AbstractMetaArgument &arg); + +protected: + OverloadDataRootNode(const AbstractMetaFunctionCList &o= {}); + + void dumpRootGraph(QTextStream &s, int minArgs, int maxArgs) const; + void sortNextOverloads(const ApiExtractorResult &api); + + +#ifndef QT_NO_DEBUG_STREAM + void formatReferenceFunction(QDebug &d) const; + void formatOverloads(QDebug &d) const; + void formatNextOverloadData(QDebug &d) const; +#endif + + AbstractMetaFunctionCList m_overloads; + OverloadDataList m_children; +}; + +/// OverloadDataNode references an argument/type combination. +class OverloadDataNode : public OverloadDataRootNode +{ +public: + explicit OverloadDataNode(const AbstractMetaFunctionCPtr &func, + OverloadDataRootNode *parent, + const AbstractMetaArgument &arg, int argPos, + const QString argTypeReplaced = {}); + void addOverload(const AbstractMetaFunctionCPtr &func); + + int argPos() const override { return m_argPos; } + const OverloadDataRootNode *parent() const override; + void dumpNodeGraph(QTextStream &s) const; + + const AbstractMetaArgument &argument() const + { return m_argument; } + const AbstractMetaType &argType() const { return m_argument.type(); } + const AbstractMetaType &modifiedArgType() const { return m_argument.modifiedType(); } + + bool isTypeModified() const { return m_argument.isTypeModified(); } + + const AbstractMetaArgument *overloadArgument(const AbstractMetaFunctionCPtr &func) const; + +#ifndef QT_NO_DEBUG_STREAM + void formatDebug(QDebug &d) const override; +#endif + +private: + AbstractMetaArgument m_argument; + QString m_argTypeReplaced; + OverloadDataRootNode *m_parent = nullptr; + + int m_argPos = -1; // Position excluding modified/removed arguments. +}; + +class OverloadData : public OverloadDataRootNode +{ +public: + explicit OverloadData(const AbstractMetaFunctionCList &overloads, + const ApiExtractorResult &api); + + int minArgs() const { return m_minArgs; } + int maxArgs() const { return m_maxArgs; } + + /// Returns true if any of the overloads for the current OverloadData has a return type different from void. + bool hasNonVoidReturnType() const; + + /// Returns true if any of the overloads for the current OverloadData has a varargs argument. + bool hasVarargs() const; + + /// Returns true if any of the overloads for the current OverloadData is static. + bool hasStaticFunction() const; + + /// Returns true if any of the overloads passed as argument is static. + static bool hasStaticFunction(const AbstractMetaFunctionCList &overloads); + + /// Returns true if any of the overloads for the current OverloadData is classmethod. + bool hasClassMethod() const; + + /// Returns true if any of the overloads passed as argument is classmethod. + static bool hasClassMethod(const AbstractMetaFunctionCList &overloads); + + /// Returns true if any of the overloads for the current OverloadData is not static. + bool hasInstanceFunction() const; + + /// Returns true if any of the overloads passed as argument is not static. + static bool hasInstanceFunction(const AbstractMetaFunctionCList &overloads); + + /// Returns true if among the overloads for the current OverloadData there are static and non-static methods altogether. + bool hasStaticAndInstanceFunctions() const; + + /// Returns true if among the overloads passed as argument there are static and non-static methods altogether. + static bool hasStaticAndInstanceFunctions(const AbstractMetaFunctionCList &overloads); + + QList<int> invalidArgumentLengths() const; + + static int numberOfRemovedArguments(const AbstractMetaFunctionCPtr &func); + static int numberOfRemovedArguments(const AbstractMetaFunctionCPtr &func, int finalArgPos); + + void dumpGraph(const QString &filename) const; + QString dumpGraph() const; + bool showGraph() const; + + /// Returns true if a list of arguments is used (METH_VARARGS) + bool pythonFunctionWrapperUsesListOfArguments() const; + + bool hasArgumentWithDefaultValue() const; + static bool hasArgumentWithDefaultValue(const AbstractMetaFunctionCPtr &func); + + /// Returns a list of function arguments which have default values and were not removed. + static AbstractMetaArgumentList getArgumentsWithDefaultValues(const AbstractMetaFunctionCPtr &func); + +#ifndef QT_NO_DEBUG_STREAM + void formatDebug(QDebug &) const override; +#endif + +private: + int m_minArgs = 256; + int m_maxArgs = 0; +}; + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug, const OverloadData &); +#endif + +#endif // OVERLOADDATA_H diff --git a/sources/shiboken6/generator/shiboken/pytypenames.h b/sources/shiboken6/generator/shiboken/pytypenames.h new file mode 100644 index 000000000..6c7658ff6 --- /dev/null +++ b/sources/shiboken6/generator/shiboken/pytypenames.h @@ -0,0 +1,29 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef PYTYPENAMES_H +#define PYTYPENAMES_H + +#include <QtCore/QString> + +constexpr auto pyBoolT = QLatin1StringView ("PyBool"); +constexpr auto pyFloatT = QLatin1StringView ("PyFloat"); +constexpr auto pyLongT = QLatin1StringView ("PyLong"); +constexpr auto pyObjectT = QLatin1StringView ("object"); +constexpr auto pyStrT = QLatin1StringView ("str"); + +// PYSIDE-1499: A custom type determined by existence of an `__fspath__` attribute. +constexpr auto pyPathLikeT = QLatin1StringView ("PyPathLike"); + +constexpr auto cPyBufferT = QLatin1StringView ("PyBuffer"); +constexpr auto cPyListT = QLatin1StringView ("PyList"); +constexpr auto cPyObjectT = QLatin1StringView ("PyObject"); +constexpr auto cPySequenceT = QLatin1StringView ("PySequence"); +constexpr auto cPyTypeObjectT = QLatin1StringView ("PyTypeObject"); + +// numpy +constexpr auto cPyArrayObjectT = QLatin1StringView ("PyArrayObject"); + +constexpr auto sbkCharT = QLatin1StringView ("SbkChar"); + +#endif // PYTYPENAMES_H diff --git a/sources/shiboken6/generator/shiboken/shibokengenerator.cpp b/sources/shiboken6/generator/shiboken/shibokengenerator.cpp new file mode 100644 index 000000000..a1417e5d9 --- /dev/null +++ b/sources/shiboken6/generator/shiboken/shibokengenerator.cpp @@ -0,0 +1,2649 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "shibokengenerator.h" +#include "generatorstrings.h" +#include "generatorargument.h" +#include "defaultvalue.h" +#include "generatorcontext.h" +#include "apiextractorresult.h" +#include "codesnip.h" +#include "customconversion.h" +#include "ctypenames.h" +#include <abstractmetabuilder.h> +#include <abstractmetaenum.h> +#include <abstractmetafield.h> +#include <abstractmetafunction.h> +#include <abstractmetalang.h> +#include <abstractmetalang_helpers.h> +#include <usingmember.h> +#include <exception.h> +#include <messages.h> +#include <modifications.h> +#include "overloaddata.h" +#include <optionsparser.h> +#include "propertyspec.h" +#include "pytypenames.h" +#include <reporthandler.h> +#include <textstream.h> +#include <typedatabase.h> +#include <containertypeentry.h> +#include <customtypenentry.h> +#include <enumtypeentry.h> +#include <flagstypeentry.h> +#include <namespacetypeentry.h> +#include <primitivetypeentry.h> +#include <pythontypeentry.h> +#include <smartpointertypeentry.h> +#include <valuetypeentry.h> + +#include <iostream> + +#include "qtcompat.h" + +#include <QtCore/QDir> +#include <QtCore/QDebug> +#include <QtCore/QRegularExpression> + +#include <algorithm> +#include <limits> +#include <memory> +#include <utility> + +using namespace Qt::StringLiterals; + +static constexpr auto PARENT_CTOR_HEURISTIC = "enable-parent-ctor-heuristic"_L1; +static constexpr auto RETURN_VALUE_HEURISTIC = "enable-return-value-heuristic"_L1; +static constexpr auto DISABLE_VERBOSE_ERROR_MESSAGES = "disable-verbose-error-messages"_L1; +static constexpr auto USE_ISNULL_AS_NB_BOOL = "use-isnull-as-nb-bool"_L1; +// FIXME PYSIDE 7: Remove USE_ISNULL_AS_NB_NONZERO/USE_OPERATOR_BOOL_AS_NB_NONZERO +static constexpr auto USE_ISNULL_AS_NB_NONZERO = "use-isnull-as-nb_nonzero"_L1; +static constexpr auto USE_OPERATOR_BOOL_AS_NB_BOOL = "use-operator-bool-as-nb-bool"_L1; +static constexpr auto USE_OPERATOR_BOOL_AS_NB_NONZERO = "use-operator-bool-as-nb-nonzero"_L1; +static constexpr auto WRAPPER_DIAGNOSTICS = "wrapper-diagnostics"_L1; +static constexpr auto NO_IMPLICIT_CONVERSIONS = "no-implicit-conversions"_L1; +static constexpr auto LEAN_HEADERS = "lean-headers"_L1; + +QString CPP_ARG_N(int i) +{ + return CPP_ARG + QString::number(i); +} + +constexpr auto CPP_ARG_REMOVED_PREFIX = "removed_cppArg"_L1; + +QString CPP_ARG_REMOVED(int i) +{ + return CPP_ARG_REMOVED_PREFIX + QString::number(i); +} + +const char *const METHOD_DEF_SENTINEL = "{nullptr, nullptr, 0, nullptr} // Sentinel\n"; +const char *const PYTHON_TO_CPPCONVERSION_STRUCT = "Shiboken::Conversions::PythonToCppConversion"; + +const char *const openTargetExternC = R"( +// Target --------------------------------------------------------- + +extern "C" { +)"; +const char *const closeExternC = "} // extern \"C\"\n\n"; +const char *const richCompareComment = + "// PYSIDE-74: By default, we redirect to object's tp_richcompare (which is `==`, `!=`).\n"; + +struct ShibokenGeneratorOptions +{ + bool useCtorHeuristic = false; + bool userReturnValueHeuristic = false; + bool verboseErrorMessagesDisabled = false; + bool useIsNullAsNbBool = false; + // FIXME PYSIDE 7 Flip m_leanHeaders default or remove? + bool leanHeaders = false; + bool useOperatorBoolAsNbBool = false; + // FIXME PYSIDE 7 Flip generateImplicitConversions default or remove? + bool generateImplicitConversions = true; + bool wrapperDiagnostics = false; +}; + +struct GeneratorClassInfoCacheEntry +{ + ShibokenGenerator::FunctionGroups functionGroups; + QList<AbstractMetaFunctionCList> numberProtocolOperators; + BoolCastFunctionOptional boolCastFunctionO; + bool needsGetattroFunction = false; +}; + +using GeneratorClassInfoCache = QHash<AbstractMetaClassCPtr, GeneratorClassInfoCacheEntry>; + +Q_GLOBAL_STATIC(GeneratorClassInfoCache, generatorClassInfoCache) + +static const char CHECKTYPE_REGEX[] = R"(%CHECKTYPE\[([^\[]*)\]\()"; +static const char ISCONVERTIBLE_REGEX[] = R"(%ISCONVERTIBLE\[([^\[]*)\]\()"; +static const char CONVERTTOPYTHON_REGEX[] = R"(%CONVERTTOPYTHON\[([^\[]*)\]\()"; +// Capture a '*' leading the variable name into the target +// so that "*valuePtr = %CONVERTTOCPP..." works as expected. +static const char CONVERTTOCPP_REGEX[] = + R"((\*?%?[a-zA-Z_][\w\.]*(?:\[[^\[^<^>]+\])*)(?:\s+)=(?:\s+)%CONVERTTOCPP\[([^\[]*)\]\()"; + +const ShibokenGenerator::TypeSystemConverterRegExps & + ShibokenGenerator::typeSystemConvRegExps() +{ + static const TypeSystemConverterRegExps result = { + QRegularExpression(QLatin1StringView(CHECKTYPE_REGEX)), + QRegularExpression(QLatin1StringView(ISCONVERTIBLE_REGEX)), + QRegularExpression(QLatin1StringView(CONVERTTOCPP_REGEX)), + QRegularExpression(QLatin1StringView(CONVERTTOPYTHON_REGEX)) + }; + return result; +} + +// Options are static to avoid duplicated handling since ShibokenGenerator +// is instantiated for HeaderGenerator and CppGenerator. +ShibokenGeneratorOptions ShibokenGenerator::m_options; + +ShibokenGenerator::ShibokenGenerator() = default; + +ShibokenGenerator::~ShibokenGenerator() = default; + +// Correspondences between primitive and Python types. +static const QHash<QString, QString> &primitiveTypesCorrespondences() +{ + static const QHash<QString, QString> result = { + {u"bool"_s, pyBoolT}, + {u"char"_s, sbkCharT}, + {u"signed char"_s, sbkCharT}, + {u"unsigned char"_s, sbkCharT}, + {intT, pyLongT}, + {u"signed int"_s, pyLongT}, + {u"uint"_s, pyLongT}, + {u"unsigned int"_s, pyLongT}, + {shortT, pyLongT}, + {u"ushort"_s, pyLongT}, + {u"signed short"_s, pyLongT}, + {u"signed short int"_s, pyLongT}, + {unsignedShortT, pyLongT}, + {u"unsigned short int"_s, pyLongT}, + {longT, pyLongT}, + {doubleT, pyFloatT}, + {floatT, pyFloatT}, + {u"unsigned long"_s, pyLongT}, + {u"signed long"_s, pyLongT}, + {u"ulong"_s, pyLongT}, + {u"unsigned long int"_s, pyLongT}, + {u"long long"_s, pyLongT}, + {u"__int64"_s, pyLongT}, + {u"unsigned long long"_s, pyLongT}, + {u"unsigned __int64"_s, pyLongT}, + {u"size_t"_s, pyLongT} + }; + return result; +} + +const QHash<QString, QChar> &ShibokenGenerator::formatUnits() +{ + static const QHash<QString, QChar> result = { + {u"char"_s, u'b'}, + {u"unsigned char"_s, u'B'}, + {intT, u'i'}, + {u"unsigned int"_s, u'I'}, + {shortT, u'h'}, + {unsignedShortT, u'H'}, + {longT, u'l'}, + {unsignedLongLongT, u'k'}, + {longLongT, u'L'}, + {u"__int64"_s, u'L'}, + {unsignedLongLongT, u'K'}, + {u"unsigned __int64"_s, u'K'}, + {doubleT, u'd'}, + {floatT, u'f'}, + }; + return result; +} + +QString ShibokenGenerator::translateTypeForWrapperMethod(const AbstractMetaType &cType, + const AbstractMetaClassCPtr &context, + Options options) const +{ + if (cType.isArray()) { + return translateTypeForWrapperMethod(*cType.arrayElementType(), context, options) + + u"[]"_s; + } + + if (avoidProtectedHack() && cType.isEnum()) { + auto metaEnum = api().findAbstractMetaEnum(cType.typeEntry()); + if (metaEnum && metaEnum->isProtected()) + return protectedEnumSurrogateName(metaEnum.value()); + } + + return translateType(cType, context, options); +} + +bool ShibokenGenerator::shouldGenerateCppWrapper(const AbstractMetaClassCPtr &metaClass) +{ + const auto wrapper = metaClass->cppWrapper(); + return wrapper.testFlag(AbstractMetaClass::CppVirtualMethodWrapper) + || (avoidProtectedHack() + && wrapper.testFlag(AbstractMetaClass::CppProtectedHackWrapper)); +} + +bool ShibokenGenerator::shouldGenerateMetaObjectFunctions(const AbstractMetaClassCPtr &metaClass) +{ + return usePySideExtensions() + && (!avoidProtectedHack() || !metaClass->hasPrivateDestructor()) + && !metaClass->typeEntry()->typeFlags() + .testFlag(ComplexTypeEntry::DisableQtMetaObjectFunctions) + && isQObject(metaClass); +} + +ShibokenGenerator::FunctionGeneration ShibokenGenerator::functionGeneration( + const AbstractMetaFunctionCPtr &func) +{ + FunctionGeneration result; + + const auto functionType = func->functionType(); + switch (functionType) { + case AbstractMetaFunction::ConversionOperator: + case AbstractMetaFunction::AssignmentOperatorFunction: + case AbstractMetaFunction::MoveAssignmentOperatorFunction: + case AbstractMetaFunction::DestructorFunction: + case AbstractMetaFunction::SignalFunction: + case AbstractMetaFunction::GetAttroFunction: + case AbstractMetaFunction::SetAttroFunction: + return result; + default: + if (func->isUserAdded() || func->usesRValueReferences() || !func->isWhiteListed()) + return result; + break; + } + + const bool notModifiedRemoved = !func->isModifiedRemoved(); + const bool isPrivate = func->isPrivate() && !func->isVisibilityModifiedToPrivate(); + switch (functionType) { + case AbstractMetaFunction::ConstructorFunction: + if (!isPrivate && notModifiedRemoved) + result.setFlag(FunctionGenerationFlag::WrapperConstructor); + return result; + case AbstractMetaFunction::CopyConstructorFunction: + if (!isPrivate && notModifiedRemoved) + result.setFlag(FunctionGenerationFlag::WrapperSpecialCopyConstructor); + return result; + case AbstractMetaFunction::NormalFunction: + case AbstractMetaFunction::SlotFunction: + if (avoidProtectedHack() && func->isProtected()) + result.setFlag(FunctionGenerationFlag::ProtectedWrapper); + break; + default: + break; + } + + // Check on virtuals (including operators). + const bool isAbstract = func->isAbstract(); + if (!(isAbstract || func->isVirtual()) + || func->cppAttributes().testFlag(FunctionAttribute::Final) + || func->isModifiedFinal()) { + return result; + } + + // MetaObject virtuals only need to be declared; CppGenerator creates a + // special implementation. + if (functionType == AbstractMetaFunction::NormalFunction + && usePySideExtensions() && isQObject(func->ownerClass())) { + const QString &name = func->name(); + if (name == u"metaObject"_s || name == u"qt_metacall") { + result.setFlag(FunctionGenerationFlag::QMetaObjectMethod); + return result; + } + } + + // Pure virtual functions need a default implementation even if private. + if (isAbstract || (notModifiedRemoved && !isPrivate)) + result.setFlag(FunctionGenerationFlag::VirtualMethod); + + return result; +} + +AbstractMetaFunctionCList ShibokenGenerator::implicitConversions(const TypeEntryCPtr &t) const +{ + if (!generateImplicitConversions() || !t->isValue()) + return {}; + auto vte = std::static_pointer_cast<const ValueTypeEntry>(t); + auto customConversion = vte->customConversion(); + if (customConversion && customConversion->replaceOriginalTargetToNativeConversions()) + return {}; + + auto result = api().implicitConversions(t); + auto end = std::remove_if(result.begin(), result.end(), + [](const AbstractMetaFunctionCPtr &f) { + return f->isUserAdded(); + }); + result.erase(end, result.end()); + return result; +} + +QString ShibokenGenerator::wrapperName(const AbstractMetaClassCPtr &metaClass) +{ + Q_ASSERT(shouldGenerateCppWrapper(metaClass)); + QString result = metaClass->name(); + if (metaClass->enclosingClass()) // is a inner class + result.replace(u"::"_s, u"_"_s); + return result + u"Wrapper"_s; +} + +QString ShibokenGenerator::fullPythonClassName(const AbstractMetaClassCPtr &metaClass) +{ + QString fullClassName = metaClass->name(); + auto enclosing = metaClass->enclosingClass(); + while (enclosing) { + if (NamespaceTypeEntry::isVisibleScope(enclosing->typeEntry())) + fullClassName.prepend(enclosing->name() + u'.'); + enclosing = enclosing->enclosingClass(); + } + fullClassName.prepend(metaClass->typeEntry()->targetLangPackage() + u'.'); + return fullClassName; +} + +QString ShibokenGenerator::headerFileNameForContext(const GeneratorContext &context) +{ + return fileNameForContextHelper(context, u"_wrapper.h"_s); +} + +// PYSIDE-500: When avoiding the protected hack, also include the inherited +// wrapper classes of the *current* module, because without the protected hack, +// we sometimes need to cast inherited wrappers. Inherited classes +// of *other* modules are completely regenerated by the header generator +// since the wrapper headers are not installed. + +IncludeGroup ShibokenGenerator::baseWrapperIncludes(const GeneratorContext &classContext) const +{ + IncludeGroup result{u"Wrappers"_s, {}}; + if (!classContext.useWrapper() || !avoidProtectedHack() + || classContext.forSmartPointer()) { + return result; + } + + const auto moduleEntry = TypeDatabase::instance()->defaultTypeSystemType(); + const auto &baseClasses = allBaseClasses(classContext.metaClass()); + for (const auto &base : baseClasses) { + const auto te = base->typeEntry(); + if (te->codeGeneration() == TypeEntry::GenerateCode) { // current module + const auto context = contextForClass(base); + if (context.useWrapper()) { + const QString header = headerFileNameForContext(context); + const auto type = typeSystemTypeEntry(te) == moduleEntry + ? Include::LocalPath : Include::IncludePath; + result.append(Include(type, header)); + } + } + } + return result; +} + +QString ShibokenGenerator::fullPythonFunctionName(const AbstractMetaFunctionCPtr &func, bool forceFunc) +{ + QString funcName; + if (func->isOperatorOverload()) + funcName = ShibokenGenerator::pythonOperatorFunctionName(func); + else + funcName = func->name(); + if (func->ownerClass()) { + QString fullClassName = fullPythonClassName(func->ownerClass()); + if (func->isConstructor()) { + funcName = fullClassName; + if (forceFunc) + funcName.append(u".__init__"_s); + } + else { + funcName.prepend(fullClassName + u'.'); + } + } + else { + funcName = packageName() + u'.' + func->name(); + } + return funcName; +} + +bool ShibokenGenerator::wrapperDiagnostics() +{ + return m_options.wrapperDiagnostics; +} + +QString ShibokenGenerator::protectedEnumSurrogateName(const AbstractMetaEnum &metaEnum) +{ + QString result = metaEnum.fullName(); + result.replace(u'.', u'_'); + result.replace(u"::"_s, u"_"_s); + return result + u"_Surrogate"_s; +} + +QString ShibokenGenerator::cpythonFunctionName(const AbstractMetaFunctionCPtr &func) +{ + QString result; + + // PYSIDE-331: For inherited functions, we need to find the same labels. + // Therefore we use the implementing class. + if (func->implementingClass()) { + result = cpythonBaseName(func->implementingClass()->typeEntry()); + if (func->isConstructor()) { + result += u"_Init"_s; + } else { + result += u"Func_"_s; + if (func->isOperatorOverload()) + result += ShibokenGenerator::pythonOperatorFunctionName(func); + else + result += func->name(); + } + } else { + result = u"Sbk"_s + moduleName() + u"Module_"_s + func->name(); + } + + return result; +} + +QString ShibokenGenerator::cpythonMethodDefinitionName(const AbstractMetaFunctionCPtr &func) +{ + if (!func->ownerClass()) + return {}; + return cpythonBaseName(func->ownerClass()->typeEntry()) + u"Method_"_s + + func->name(); +} + +QString ShibokenGenerator::cpythonGettersSettersDefinitionName(const AbstractMetaClassCPtr &metaClass) +{ + return cpythonBaseName(metaClass) + u"_getsetlist"_s; +} + +QString ShibokenGenerator::cpythonSetattroFunctionName(const AbstractMetaClassCPtr &metaClass) +{ + return cpythonBaseName(metaClass) + u"_setattro"_s; +} + + +QString ShibokenGenerator::cpythonGetattroFunctionName(const AbstractMetaClassCPtr &metaClass) +{ + return cpythonBaseName(metaClass) + u"_getattro"_s; +} + +QString ShibokenGenerator::cpythonGetterFunctionName(const QString &name, + const AbstractMetaClassCPtr &enclosingClass) +{ + return cpythonBaseName(enclosingClass) + "_get_"_L1 + name; +} + +QString ShibokenGenerator::cpythonSetterFunctionName(const QString &name, + const AbstractMetaClassCPtr &enclosingClass) +{ + return cpythonBaseName(enclosingClass) + "_set_"_L1 + name; +} + +QString ShibokenGenerator::cpythonGetterFunctionName(const AbstractMetaField &metaField) +{ + return cpythonGetterFunctionName(metaField.name(), metaField.enclosingClass()); +} + +QString ShibokenGenerator::cpythonSetterFunctionName(const AbstractMetaField &metaField) +{ + return cpythonSetterFunctionName(metaField.name(), metaField.enclosingClass()); +} + +QString ShibokenGenerator::cpythonGetterFunctionName(const QPropertySpec &property, + const AbstractMetaClassCPtr &metaClass) +{ + return cpythonGetterFunctionName(property.name(), metaClass); +} + +QString ShibokenGenerator::cpythonSetterFunctionName(const QPropertySpec &property, + const AbstractMetaClassCPtr &metaClass) +{ + return cpythonSetterFunctionName(property.name(), metaClass); +} + +static QString cpythonEnumFlagsName(const QString &moduleName, + const QString &qualifiedCppName) +{ + QString result = u"Sbk"_s + moduleName + u'_' + qualifiedCppName; + result.replace(u"::"_s, u"_"_s); + return result; +} + +QString ShibokenGenerator::cpythonEnumName(const EnumTypeEntryCPtr &enumEntry) +{ + QString p = enumEntry->targetLangPackage(); + p.replace(u'.', u'_'); + return cpythonEnumFlagsName(p, enumEntry->qualifiedCppName()); +} + +QString ShibokenGenerator::cpythonEnumName(const AbstractMetaEnum &metaEnum) +{ + return cpythonEnumName(metaEnum.typeEntry()); +} + +QString ShibokenGenerator::cpythonFlagsName(const FlagsTypeEntryCPtr &flagsEntry) +{ + QString p = flagsEntry->targetLangPackage(); + p.replace(u'.', u'_'); + return cpythonEnumFlagsName(p, flagsEntry->originalName()); +} + +QString ShibokenGenerator::cpythonFlagsName(const AbstractMetaEnum *metaEnum) +{ + const auto flags = metaEnum->typeEntry()->flags(); + return flags ? cpythonFlagsName(flags) : QString{}; +} + +QString ShibokenGenerator::cpythonSpecialCastFunctionName(const AbstractMetaClassCPtr &metaClass) +{ + return cpythonBaseName(metaClass->typeEntry()) + u"SpecialCastFunction"_s; +} + +QString ShibokenGenerator::cpythonWrapperCPtr(const AbstractMetaClassCPtr &metaClass, + const QString &argName) +{ + return cpythonWrapperCPtr(metaClass->typeEntry(), argName); +} + +QString ShibokenGenerator::cpythonWrapperCPtr(const AbstractMetaType &metaType, + const QString &argName) +{ + if (!metaType.isWrapperType()) + return {}; + return u"reinterpret_cast< ::"_s + metaType.cppSignature() + + u" *>(Shiboken::Conversions::cppPointer("_s + cpythonTypeNameExt(metaType) + + u", reinterpret_cast<SbkObject *>("_s + argName + u")))"_s; +} + +QString ShibokenGenerator::cpythonWrapperCPtr(const TypeEntryCPtr &type, + const QString &argName) +{ + if (!type->isWrapperType()) + return QString(); + return u"reinterpret_cast< "_s + getFullTypeName(type) + + u" *>(Shiboken::Conversions::cppPointer("_s + cpythonTypeNameExt(type) + + u", reinterpret_cast<SbkObject *>("_s + argName + u")))"_s; +} + +void ShibokenGenerator::writeToPythonConversion(TextStream & s, const AbstractMetaType &type, + const AbstractMetaClassCPtr & /* context */, + const QString &argumentName) +{ + s << cpythonToPythonConversionFunction(type) << argumentName << ')'; +} + +void ShibokenGenerator::writeToCppConversion(TextStream &s, + const AbstractMetaClassCPtr &metaClass, + const QString &inArgName, const QString &outArgName) +{ + s << cpythonToCppConversionFunction(metaClass) << inArgName << ", &" << outArgName << ')'; +} + +void ShibokenGenerator::writeToCppConversion(TextStream &s, const AbstractMetaType &type, + const QString &inArgName, + const QString &outArgName) +{ + s << cpythonToCppConversionFunction(type) << inArgName << ", &" << outArgName << ')'; +} + +bool ShibokenGenerator::shouldRejectNullPointerArgument(const AbstractMetaFunctionCPtr &func, + int argIndex) +{ + if (argIndex < 0 || argIndex >= func->arguments().size()) + return false; + + const AbstractMetaArgument &arg = func->arguments().at(argIndex); + if (arg.type().isValueTypeWithCopyConstructorOnly()) + return true; + + // Argument type is not a pointer, a None rejection should not be + // necessary because the type checking would handle that already. + if (!arg.type().isPointer()) + return false; + if (arg.isModifiedRemoved()) + return false; + for (const auto &funcMod : func->modifications()) { + for (const ArgumentModification &argMod : funcMod.argument_mods()) { + if (argMod.index() == argIndex + 1 && argMod.noNullPointers()) + return true; + } + } + return false; +} + +QString ShibokenGenerator::cpythonBaseName(const AbstractMetaType &type) +{ + if (type.isCString()) + return u"PyString"_s; + return cpythonBaseName(type.typeEntry()); +} + +QString ShibokenGenerator::cpythonBaseName(const AbstractMetaClassCPtr &metaClass) +{ + return cpythonBaseName(metaClass->typeEntry()); +} + +QString ShibokenGenerator::containerCpythonBaseName(const ContainerTypeEntryCPtr &ctype) +{ + switch (ctype->containerKind()) { + case ContainerTypeEntry::SetContainer: + return u"PySet"_s; + case ContainerTypeEntry::MapContainer: + case ContainerTypeEntry::MultiMapContainer: + return u"PyDict"_s; + case ContainerTypeEntry::ListContainer: + case ContainerTypeEntry::PairContainer: + case ContainerTypeEntry::SpanContainer: + break; + default: + Q_ASSERT(false); + } + return cPySequenceT; +} + +QString ShibokenGenerator::cpythonBaseName(const TypeEntryCPtr &type) +{ + QString baseName; + if (type->isWrapperType() || type->isNamespace()) { // && type->referenceType() == NoReference) { + baseName = u"Sbk_"_s + type->name(); + } else if (type->isPrimitive()) { + const auto ptype = basicReferencedTypeEntry(type); + baseName = ptype->hasTargetLangApiType() + ? ptype->targetLangApiName() : pythonPrimitiveTypeName(ptype->name()); + } else if (type->isEnum()) { + baseName = cpythonEnumName(std::static_pointer_cast<const EnumTypeEntry>(type)); + } else if (type->isFlags()) { + baseName = cpythonFlagsName(std::static_pointer_cast<const FlagsTypeEntry>(type)); + } else if (type->isContainer()) { + const auto ctype = std::static_pointer_cast<const ContainerTypeEntry>(type); + baseName = containerCpythonBaseName(ctype); + } else { + baseName = cPyObjectT; + } + return baseName.replace(u"::"_s, u"_"_s); +} + +QString ShibokenGenerator::cpythonTypeName(const AbstractMetaClassCPtr &metaClass) +{ + return cpythonTypeName(metaClass->typeEntry()); +} + +QString ShibokenGenerator::cpythonTypeName(const TypeEntryCPtr &type) +{ + return cpythonBaseName(type) + u"_TypeF()"_s; +} + +QString ShibokenGenerator::converterObject(const AbstractMetaType &type) +{ + if (type.isCString()) + return u"Shiboken::Conversions::PrimitiveTypeConverter<const char *>()"_s; + if (type.isVoidPointer()) + return u"Shiboken::Conversions::PrimitiveTypeConverter<void *>()"_s; + const AbstractMetaTypeList nestedArrayTypes = type.nestedArrayTypes(); + if (!nestedArrayTypes.isEmpty() && nestedArrayTypes.constLast().isCppPrimitive()) { + return "Shiboken::Conversions::ArrayTypeConverter<"_L1 + + nestedArrayTypes.constLast().minimalSignature() + + u">("_s + QString::number(nestedArrayTypes.size()) + + u')'; + } + + auto typeEntry = type.typeEntry(); + if (typeEntry->isContainer() || typeEntry->isSmartPointer()) { + return convertersVariableName(typeEntry->targetLangPackage()) + + u'[' + getTypeIndexVariableName(type) + u']'; + } + return converterObject(typeEntry); +} + +QString ShibokenGenerator::converterObject(const TypeEntryCPtr &type) +{ + if (isExtendedCppPrimitive(type)) + return QString::fromLatin1("Shiboken::Conversions::PrimitiveTypeConverter<%1>()") + .arg(type->qualifiedCppName()); + if (type->isWrapperType()) + return QString::fromLatin1("PepType_SOTP(reinterpret_cast<PyTypeObject *>(%1))->converter") + .arg(cpythonTypeNameExt(type)); + if (type->isEnum() || type->isFlags()) + return QString::fromLatin1("PepType_SETP(reinterpret_cast<SbkEnumType *>(%1))->converter") + .arg(cpythonTypeNameExt(type)); + + if (type->isArray()) { + qDebug() << "Warning: no idea how to handle the Qt5 type " << type->qualifiedCppName(); + return {}; + } + + /* the typedef'd primitive types case */ + auto pte = std::dynamic_pointer_cast<const PrimitiveTypeEntry>(type); + if (!pte) { + qDebug() << "Warning: the Qt5 primitive type is unknown" << type->qualifiedCppName(); + return {}; + } + pte = basicReferencedTypeEntry(pte); + if (pte->isPrimitive() && !isCppPrimitive(pte) && !pte->customConversion()) { + return u"Shiboken::Conversions::PrimitiveTypeConverter<"_s + + pte->qualifiedCppName() + u">()"_s; + } + + return convertersVariableName(type->targetLangPackage()) + + u'[' + getTypeIndexVariableName(type) + u']'; +} + +QString ShibokenGenerator::cpythonTypeNameExtSet(const TypeEntryCPtr &type) +{ + return cppApiVariableName(type->targetLangPackage()) + u'[' + + getTypeIndexVariableName(type) + "].type"_L1; +} + +QString ShibokenGenerator::cpythonTypeNameExtSet(const AbstractMetaType &type) +{ + return cppApiVariableName(type.typeEntry()->targetLangPackage()) + u'[' + + getTypeIndexVariableName(type) + "].type"_L1; +} + +QString ShibokenGenerator::cpythonTypeNameExt(const TypeEntryCPtr &type) +{ + return "Shiboken::Module::get("_L1 + cppApiVariableName(type->targetLangPackage()) + + u'[' + getTypeIndexVariableName(type) + "])"_L1; +} + +QString ShibokenGenerator::cpythonTypeNameExt(const AbstractMetaType &type) +{ + return u"Shiboken::Module::get("_s + cppApiVariableName(type.typeEntry()->targetLangPackage()) + + u'[' + getTypeIndexVariableName(type) + "])"_L1; +} + +QString ShibokenGenerator::fixedCppTypeName(const TargetToNativeConversion &toNative) +{ + if (toNative.sourceType()) + return fixedCppTypeName(toNative.sourceType()); + return toNative.sourceTypeName(); +} +QString ShibokenGenerator::fixedCppTypeName(const AbstractMetaType &type) +{ + return fixedCppTypeName(type.typeEntry(), type.cppSignature()); +} + +static QString _fixedCppTypeName(QString typeName) +{ + typeName.remove(u' '); + typeName.replace(u'.', u'_'); + typeName.replace(u',', u'_'); + typeName.replace(u'<', u'_'); + typeName.replace(u'>', u'_'); + typeName.replace(u"::"_s, u"_"_s); + typeName.replace(u"*"_s, u"PTR"_s); + typeName.replace(u"&"_s, u"REF"_s); + return typeName; +} +QString ShibokenGenerator::fixedCppTypeName(const TypeEntryCPtr &type, QString typeName) +{ + if (typeName.isEmpty()) + typeName = type->qualifiedCppName(); + if (!type->generateCode()) { + typeName.prepend(u'_'); + typeName.prepend(type->targetLangPackage()); + } + return _fixedCppTypeName(typeName); +} + +QString ShibokenGenerator::pythonPrimitiveTypeName(const QString &cppTypeName) +{ + const auto &mapping = primitiveTypesCorrespondences(); + const auto it = mapping.constFind(cppTypeName); + if (it == mapping.cend()) + throw Exception(u"Primitive type not found: "_s + cppTypeName); + return it.value(); +} + +QString ShibokenGenerator::pythonOperatorFunctionName(const AbstractMetaFunctionCPtr &func) +{ + QString op = Generator::pythonOperatorFunctionName(func->originalName()); + if (op.isEmpty()) { + qCWarning(lcShiboken).noquote().nospace() << msgUnknownOperator(func.get()); + return "__UNKNOWN_OPERATOR__"_L1; + } + if (func->arguments().isEmpty()) { + if (op == u"__sub__") + op = u"__neg__"_s; + else if (op == u"__add__") + op = u"__pos__"_s; + } else if (func->isStatic() && func->arguments().size() == 2) { + // If a operator overload function has 2 arguments and + // is static we assume that it is a reverse operator. + op = op.insert(2, u'r'); + } + return op; +} + +bool ShibokenGenerator::isNumber(const QString &cpythonApiName) +{ + return cpythonApiName == pyFloatT || cpythonApiName == pyLongT + || cpythonApiName == pyBoolT; +} + +static std::optional<TypeSystem::CPythonType> + targetLangApiCPythonType(const PrimitiveTypeEntryCPtr &t) +{ + if (!t->hasTargetLangApiType()) + return {}; + const auto cte = t->targetLangApiType(); + if (cte->type() != TypeEntry::PythonType) + return {}; + return std::static_pointer_cast<const PythonTypeEntry>(cte)->cPythonType(); +} + +bool ShibokenGenerator::isNumber(const TypeEntryCPtr &type) +{ + if (!type->isPrimitive()) + return false; + const auto pte = basicReferencedTypeEntry(type); + const auto cPythonTypeOpt = targetLangApiCPythonType(pte); + // FIXME PYSIDE-1660: Return false here after making primitive types built-in? + if (!cPythonTypeOpt.has_value()) { + const auto &mapping = primitiveTypesCorrespondences(); + const auto it = mapping.constFind(pte->name()); + return it != mapping.cend() && isNumber(it.value()); + } + const auto cPythonType = cPythonTypeOpt.value(); + return cPythonType == TypeSystem::CPythonType::Bool + || cPythonType == TypeSystem::CPythonType::Float + || cPythonType == TypeSystem::CPythonType::Integer; +} + +bool ShibokenGenerator::isNumber(const AbstractMetaType &type) +{ + return isNumber(type.typeEntry()); +} + +bool ShibokenGenerator::isPyInt(const TypeEntryCPtr &type) +{ + if (!type->isPrimitive()) + return false; + const auto pte = basicReferencedTypeEntry(type); + const auto cPythonTypeOpt = targetLangApiCPythonType(pte); + // FIXME PYSIDE-1660: Return false here after making primitive types built-in? + if (!cPythonTypeOpt.has_value()) { + const auto &mapping = primitiveTypesCorrespondences(); + const auto it = mapping.constFind(pte->name()); + return it != mapping.cend() && it.value() == pyLongT; + } + return cPythonTypeOpt.value() == TypeSystem::CPythonType::Integer; +} + +bool ShibokenGenerator::isPyInt(const AbstractMetaType &type) +{ + return isPyInt(type.typeEntry()); +} + +bool ShibokenGenerator::isNullPtr(const QString &value) +{ + return value == u"0" || value == u"nullptr" + || value == u"NULLPTR" || value == u"{}"; +} + +QString ShibokenGenerator::cpythonCheckFunction(AbstractMetaType metaType) +{ + const auto typeEntry = metaType.typeEntry(); + if (typeEntry->isCustom()) { + const auto cte = std::static_pointer_cast<const CustomTypeEntry>(typeEntry); + if (cte->hasCheckFunction()) + return cte->checkFunction(); + throw Exception(msgUnknownCheckFunction(typeEntry)); + } + + if (metaType.isExtendedCppPrimitive()) { + if (metaType.isCString()) + return u"Shiboken::String::check"_s; + if (metaType.isVoidPointer()) + return u"true"_s; + return cpythonCheckFunction(typeEntry); + } + + if (typeEntry->isContainer()) { + QString typeCheck = u"Shiboken::Conversions::"_s; + ContainerTypeEntry::ContainerKind type = + std::static_pointer_cast<const ContainerTypeEntry>(typeEntry)->containerKind(); + if (type == ContainerTypeEntry::ListContainer + || type == ContainerTypeEntry::SetContainer) { + const QString containerType = type == ContainerTypeEntry::SetContainer + ? u"Iterable"_s : u"Sequence"_s; + const AbstractMetaType &type = metaType.instantiations().constFirst(); + if (type.isPointerToWrapperType()) { + typeCheck += u"check"_s + containerType + u"Types("_s + + cpythonTypeNameExt(type) + u", "_s; + } else if (type.isWrapperType()) { + typeCheck += u"convertible"_s + containerType + + u"Types("_s + cpythonTypeNameExt(type) + u", "_s; + } else { + typeCheck += u"convertible"_s + containerType + + u"Types("_s + converterObject(type) + u", "_s; + } + } else if (type == ContainerTypeEntry::MapContainer + || type == ContainerTypeEntry::MultiMapContainer + || type == ContainerTypeEntry::PairContainer) { + + QString pyType; + if (type == ContainerTypeEntry::PairContainer) + pyType = u"Pair"_s; + else if (type == ContainerTypeEntry::MultiMapContainer) + pyType = u"MultiDict"_s; + else + pyType = u"Dict"_s; + + const AbstractMetaType &firstType = metaType.instantiations().constFirst(); + const AbstractMetaType &secondType = metaType.instantiations().constLast(); + if (firstType.isPointerToWrapperType() && secondType.isPointerToWrapperType()) { + QTextStream(&typeCheck) << "check" << pyType << "Types(" + << cpythonTypeNameExt(firstType) << ", " + << cpythonTypeNameExt(secondType) << ", "; + } else { + QTextStream(&typeCheck) << "convertible" << pyType << "Types(" + << converterObject(firstType) << ", " + << (firstType.isPointerToWrapperType() ? "true" : "false") + << ", " << converterObject(secondType) << ", " + << (secondType.isPointerToWrapperType() ? "true" :"false") + << ", "; + } + } + return typeCheck; + } + return cpythonCheckFunction(typeEntry); +} + +QString ShibokenGenerator::cpythonCheckFunction(TypeEntryCPtr type) +{ + if (type->isCustom()) { + const auto cte = std::static_pointer_cast<const CustomTypeEntry>(type); + if (cte->hasCheckFunction()) + return cte->checkFunction(); + throw Exception(msgUnknownCheckFunction(type)); + } + + if (type->isEnum() || type->isFlags() || type->isWrapperType()) + return u"SbkObject_TypeCheck("_s + cpythonTypeNameExt(type) + u", "_s; + + if (type->isPrimitive()) + type = basicReferencedTypeEntry(type); + + if (auto tla = type->targetLangApiType()) { + if (tla->hasCheckFunction()) + return tla->checkFunction(); + } + + if (isExtendedCppPrimitive(type)) + return pythonPrimitiveTypeName(type->name()) + u"_Check"_s; + + return cpythonIsConvertibleFunction(type); +} + +QString ShibokenGenerator::cpythonIsConvertibleFunction(const TypeEntryCPtr &type) +{ + if (type->isWrapperType()) { + QString result = u"Shiboken::Conversions::"_s; + bool isValue = false; + if (type->isValue()) { + const auto cte = std::static_pointer_cast<const ComplexTypeEntry>(type); + isValue = !cte->isValueTypeWithCopyConstructorOnly(); + } + result += isValue ? u"isPythonToCppValueConvertible"_s + : u"isPythonToCppPointerConvertible"_s; + result += u"("_s + cpythonTypeNameExt(type) + u", "_s; + return result; + } + return QString::fromLatin1("Shiboken::Conversions::isPythonToCppConvertible(%1, ") + .arg(converterObject(type)); +} + +QString ShibokenGenerator::cpythonIsConvertibleFunction(const AbstractMetaType &metaType) +{ + const auto typeEntry = metaType.typeEntry(); + if (typeEntry->isCustom()) { + const auto cte = std::static_pointer_cast<const CustomTypeEntry>(typeEntry); + if (cte->hasCheckFunction()) + return cte->checkFunction(); + throw Exception(msgUnknownCheckFunction(typeEntry)); + } + + QString result = u"Shiboken::Conversions::"_s; + if (metaType.generateOpaqueContainer()) { + result += u"pythonToCppReferenceConversion("_s + + converterObject(metaType) + u", "_s; + return result; + } + if (metaType.isWrapperType()) { + if (metaType.isPointer() || metaType.isValueTypeWithCopyConstructorOnly()) { + result += u"pythonToCppPointerConversion"_s; + } else if (metaType.referenceType() == LValueReference + || (metaType.referenceType() == RValueReference && typeEntry->isObject())) { + result += u"pythonToCppReferenceConversion"_s; + } else { + result += u"pythonToCppValueConversion"_s; + } + result += u'(' + cpythonTypeNameExt(metaType) + u", "_s; + return result; + } + result += u"pythonToCppConversion("_s + converterObject(metaType); + // Write out array sizes if known + const AbstractMetaTypeList nestedArrayTypes = metaType.nestedArrayTypes(); + if (!nestedArrayTypes.isEmpty() && nestedArrayTypes.constLast().isCppPrimitive()) { + const int dim1 = metaType.arrayElementCount(); + const int dim2 = nestedArrayTypes.constFirst().isArray() + ? nestedArrayTypes.constFirst().arrayElementCount() : -1; + result += u", "_s + QString::number(dim1) + + u", "_s + QString::number(dim2); + } + result += u", "_s; + return result; +} + +QString ShibokenGenerator::cpythonIsConvertibleFunction(const AbstractMetaArgument &metaArg) +{ + return cpythonIsConvertibleFunction(metaArg.type()); +} + +QString ShibokenGenerator::cpythonToCppConversionFunction(const AbstractMetaClassCPtr &metaClass) +{ + return u"Shiboken::Conversions::pythonToCppPointer("_s + + cpythonTypeNameExt(metaClass->typeEntry()) + u", "_s; +} + +QString ShibokenGenerator::cpythonToCppConversionFunction(const AbstractMetaType &type) +{ + if (type.isWrapperType()) { + return u"Shiboken::Conversions::pythonToCpp"_s + + (type.isPointer() ? u"Pointer"_s : u"Copy"_s) + + u'(' + cpythonTypeNameExt(type) + u", "_s; + } + return "Shiboken::Conversions::pythonToCppCopy("_L1 + + converterObject(type) + ", "_L1; +} + +QString ShibokenGenerator::cpythonToPythonConversionFunction(const AbstractMetaType &type) +{ + if (type.isWrapperType()) { + QString conversion; + if (type.referenceType() == LValueReference + && !(type.isValue() && type.isConstant()) && !type.isPointer()) { + conversion = u"reference"_s; + } else if (type.isValue() || type.isSmartPointer()) { + conversion = u"copy"_s; + } else { + conversion = u"pointer"_s; + } + QString result = u"Shiboken::Conversions::"_s + conversion + + u"ToPython("_s + + cpythonTypeNameExt(type) + u", "_s; + if (conversion != u"pointer") + result += u'&'; + return result; + } + + const auto indirections = type.indirections() - 1; + return u"Shiboken::Conversions::copyToPython("_s + converterObject(type) + + u", "_s + AbstractMetaType::dereferencePrefix(indirections); +} + +QString ShibokenGenerator::cpythonToPythonConversionFunction(const AbstractMetaClassCPtr &metaClass) +{ + return cpythonToPythonConversionFunction(metaClass->typeEntry()); +} + +QString ShibokenGenerator::cpythonToPythonConversionFunction(const TypeEntryCPtr &type) +{ + if (type->isWrapperType()) { + const QString conversion = type->isValue() ? u"copy"_s : u"pointer"_s; + QString result = u"Shiboken::Conversions::"_s + conversion + + u"ToPython("_s + cpythonTypeNameExt(type) + + u", "_s; + if (conversion != u"pointer") + result += u'&'; + return result; + } + + return u"Shiboken::Conversions::copyToPython("_s + + converterObject(type) + u", &"_s; +} + +QString ShibokenGenerator::argumentString(const AbstractMetaFunctionCPtr &func, + const AbstractMetaArgument &argument, + Options options) const +{ + auto type = options.testFlag(OriginalTypeDescription) + ? argument.type() : argument.modifiedType(); + + + QString arg = translateType(type, func->implementingClass(), options); + + if (argument.isTypeModified()) + arg.replace(u'$', u'.'); // Haehh? + + // "int a", "int a[]" + const auto arrayPos = arg.indexOf(u'['); + if (arrayPos != -1) + arg.insert(arrayPos, u' ' + argument.name()); + else + arg.append(u' ' + argument.name()); + + if ((options & Generator::SkipDefaultValues) != Generator::SkipDefaultValues && + !argument.originalDefaultValueExpression().isEmpty()) + { + QString default_value = argument.originalDefaultValueExpression(); + if (default_value == u"NULL") + default_value = NULL_PTR; + + //WORKAROUND: fix this please + if (default_value.startsWith(u"new ")) + default_value.remove(0, 4); + + arg += u" = "_s + default_value; + } + + return arg; +} + +void ShibokenGenerator::writeArgument(TextStream &s, + const AbstractMetaFunctionCPtr &func, + const AbstractMetaArgument &argument, + Options options) const +{ + s << argumentString(func, argument, options); +} + +void ShibokenGenerator::writeFunctionArguments(TextStream &s, + const AbstractMetaFunctionCPtr &func, + Options options) const +{ + int argUsed = 0; + if (func->isUserAddedPythonOverride()) { + s << "Shiboken::GilState &gil, PyObject *" << PYTHON_OVERRIDE_VAR; + argUsed += 2; + } + for (const auto &arg : func->arguments()) { + if (options.testFlag(Generator::SkipRemovedArguments) && arg.isModifiedRemoved()) + continue; + + if (argUsed != 0) + s << ", "; + writeArgument(s, func, arg, options); + argUsed++; + } +} + +GeneratorContext ShibokenGenerator::contextForClass(const AbstractMetaClassCPtr &c) const +{ + GeneratorContext result = Generator::contextForClass(c); + if (shouldGenerateCppWrapper(c)) { + result.m_type = GeneratorContext::WrappedClass; + result.m_wrappername = wrapperName(c); + } + return result; +} + +QString ShibokenGenerator::functionReturnType(const AbstractMetaFunctionCPtr &func, Options options) const +{ + if (func->isTypeModified() && !options.testFlag(OriginalTypeDescription)) + return func->modifiedTypeName(); + return translateType(func->type(), func->implementingClass(), options); +} + +QString ShibokenGenerator::functionSignature(const AbstractMetaFunctionCPtr &func, + const QString &prepend, + const QString &append, + Options options, + int /* argCount */) const +{ + StringStream s(TextStream::Language::Cpp); + // The actual function + if (!options.testFlag(Option::SkipDefaultValues) && func->isStatic()) // Declaration + s << "static "; + if (func->isEmptyFunction() || func->needsReturnType()) + s << functionReturnType(func, options) << ' '; + else + options |= Generator::SkipReturnType; + + // name + QString name(func->originalName()); + if (func->isConstructor()) + name = wrapperName(func->ownerClass()); + + s << prepend << name << append << '('; + writeFunctionArguments(s, func, options); + s << ')'; + + if (func->isConstant()) + s << " const"; + + if (func->exceptionSpecification() == ExceptionSpecification::NoExcept) + s << " noexcept"; + + return s; +} + +void ShibokenGenerator::writeArgumentNames(TextStream &s, + const AbstractMetaFunctionCPtr &func, + Options options) +{ + const AbstractMetaArgumentList arguments = func->arguments(); + int argCount = 0; + for (const auto &argument : arguments) { + const int index = argument.argumentIndex() + 1; + if (options.testFlag(Generator::SkipRemovedArguments) && argument.isModifiedRemoved()) + continue; + const auto &type = argument.type(); + if (argCount > 0) + s << ", "; + const bool isVirtualCall = options.testFlag(Option::VirtualCall); + const bool useStdMove = isVirtualCall && type.isUniquePointer() && type.passByValue(); + s << (useStdMove ? stdMove(argument.name()) : argument.name()); + + if (!isVirtualCall + && (func->hasConversionRule(TypeSystem::NativeCode, index) + || func->hasConversionRule(TypeSystem::TargetLangCode, index)) + && !func->isConstructor()) { + s << CONV_RULE_OUT_VAR_SUFFIX; + } + + argCount++; + } +} + +void ShibokenGenerator::writeFunctionCall(TextStream &s, + const AbstractMetaFunctionCPtr &func, + Options options) +{ + s << (func->isConstructor() ? func->ownerClass()->qualifiedCppName() : func->originalName()) + << '('; + writeArgumentNames(s, func, options); + s << ')'; +} + +ShibokenGenerator::ExtendedConverterData ShibokenGenerator::getExtendedConverters() const +{ + ExtendedConverterData extConvs; + for (const auto &metaClass : api().classes()) { + // Use only the classes for the current module. + if (!shouldGenerate(metaClass->typeEntry())) + continue; + const auto &overloads = metaClass->operatorOverloads(OperatorQueryOption::ConversionOp); + for (const auto &convOp : overloads) { + // Get only the conversion operators that return a type from another module, + // that are value-types and were not removed in the type system. + const auto convType = convOp->type().typeEntry(); + if (convType->generateCode() || !convType->isValue() + || convOp->isModifiedRemoved()) + continue; + extConvs[convType].append(convOp->ownerClass()); + } + } + return extConvs; +} + +QList<CustomConversionPtr> ShibokenGenerator::getPrimitiveCustomConversions() +{ + QList<CustomConversionPtr> conversions; + const auto &primitiveTypeList = primitiveTypes(); + for (const auto &type : primitiveTypeList) { + if (type->shouldGenerate() && isUserPrimitive(type) && type->hasCustomConversion()) + conversions << type->customConversion(); + } + return conversions; +} + +static QString getArgumentsFromMethodCall(const QString &str) +{ + // It would be way nicer to be able to use a Perl like + // regular expression that accepts temporary variables + // to count the parenthesis. + // For more information check this: + // http://perl.plover.com/yak/regex/samples/slide083.html + static QLatin1String funcCall("%CPPSELF.%FUNCTION_NAME"); + auto pos = str.indexOf(funcCall); + if (pos == -1) + return QString(); + pos = pos + funcCall.size(); + while (str.at(pos) == u' ' || str.at(pos) == u'\t') + ++pos; + if (str.at(pos) == u'(') + ++pos; + int begin = pos; + int counter = 1; + while (counter != 0) { + if (str.at(pos) == u'(') + ++counter; + else if (str.at(pos) == u')') + --counter; + ++pos; + } + return str.mid(begin, pos-begin-1); +} + +QString ShibokenGenerator::getCodeSnippets(const CodeSnipList &codeSnips, + TypeSystem::CodeSnipPosition position, + TypeSystem::Language language) +{ + QString code; + for (const CodeSnip &snip : codeSnips) { + if ((position != TypeSystem::CodeSnipPositionAny && snip.position != position) || !(snip.language & language)) + continue; + code.append(snip.code()); + } + return code; +} + +void ShibokenGenerator::processClassCodeSnip(QString &code, const GeneratorContext &context) const +{ + auto metaClass = context.metaClass(); + // Replace template variable by the Python Type object + // for the class context in which the variable is used. + code.replace(u"%PYTHONTYPEOBJECT"_s, + u"(*"_s + cpythonTypeName(metaClass) + u')'); + const QString className = context.effectiveClassName(); + code.replace(u"%TYPE"_s, className); + code.replace(u"%CPPTYPE"_s, metaClass->name()); + + processCodeSnip(code, context.effectiveClassName()); +} + +void ShibokenGenerator::processCodeSnip(QString &code) const +{ + // replace "toPython" converters + replaceConvertToPythonTypeSystemVariable(code); + + // replace "toCpp" converters + replaceConvertToCppTypeSystemVariable(code); + + // replace "isConvertible" check + replaceIsConvertibleToCppTypeSystemVariable(code); + + // replace "checkType" check + replaceTypeCheckTypeSystemVariable(code); +} + +void ShibokenGenerator::processCodeSnip(QString &code, const QString &context) const +{ + try { + processCodeSnip(code); + } catch (const std::exception &e) { + throw Exception(msgSnippetError(context, e.what())); + } +} + +ShibokenGenerator::ArgumentVarReplacementList + ShibokenGenerator::getArgumentReplacement(const AbstractMetaFunctionCPtr &func, + bool usePyArgs, TypeSystem::Language language, + const AbstractMetaArgument *lastArg) +{ + ArgumentVarReplacementList argReplacements; + TypeSystem::Language convLang = (language == TypeSystem::TargetLangCode) + ? TypeSystem::NativeCode : TypeSystem::TargetLangCode; + int removed = 0; + for (qsizetype i = 0; i < func->arguments().size(); ++i) { + const AbstractMetaArgument &arg = func->arguments().at(i); + QString argValue; + if (language == TypeSystem::TargetLangCode) { + const bool hasConversionRule = func->hasConversionRule(convLang, i + 1); + const bool argRemoved = arg.isModifiedRemoved(); + if (argRemoved) + ++removed; + if (argRemoved && hasConversionRule) + argValue = arg.name() + CONV_RULE_OUT_VAR_SUFFIX; + else if (argRemoved || (lastArg && arg.argumentIndex() > lastArg->argumentIndex())) + argValue = CPP_ARG_REMOVED(i); + if (!argRemoved && argValue.isEmpty()) { + int argPos = i - removed; + AbstractMetaType type = arg.modifiedType(); + if (type.typeEntry()->isCustom()) { + argValue = usePyArgs + ? pythonArgsAt(argPos) : PYTHON_ARG; + } else { + argValue = hasConversionRule + ? arg.name() + CONV_RULE_OUT_VAR_SUFFIX + : CPP_ARG_N(argPos); + const auto generatorArg = GeneratorArgument::fromMetaType(type); + AbstractMetaType::applyDereference(&argValue, generatorArg.indirections); + } + } + } else { + argValue = arg.name(); + } + if (!argValue.isEmpty()) + argReplacements << ArgumentVarReplacementPair(arg, argValue); + + } + return argReplacements; +} + +void ShibokenGenerator::writeClassCodeSnips(TextStream &s, + const CodeSnipList &codeSnips, + TypeSystem::CodeSnipPosition position, + TypeSystem::Language language, + const GeneratorContext &context) const +{ + QString code = getCodeSnippets(codeSnips, position, language); + if (code.isEmpty()) + return; + processClassCodeSnip(code, context); + s << "// Begin code injection\n" << code << "// End of code injection\n\n"; +} + +void ShibokenGenerator::writeCodeSnips(TextStream &s, + const CodeSnipList &codeSnips, + TypeSystem::CodeSnipPosition position, + TypeSystem::Language language) const +{ + QString code = getCodeSnippets(codeSnips, position, language); + if (code.isEmpty()) + return; + processCodeSnip(code); + s << "// Begin code injection\n" << code << "// End of code injection\n\n"; +} + +static void replacePyArg0(TypeSystem::Language language, QString *code) +{ + static constexpr auto pyArg0 = "%PYARG_0"_L1; + + if (!code->contains(pyArg0)) + return; + if (language != TypeSystem::NativeCode) { + code->replace(pyArg0, PYTHON_RETURN_VAR); + return; + } + + // pyResult is an AutoDecRef in overridden methods of wrapper classes which + // has a cast operator for PyObject *. This may however not work in all + // situations (fex _PyVarObject_CAST(op) defined as ((PyVarObject*)(op))). + // Append ".object()" unless it is followed by a '.' indicating explicit + // AutoDecRef member invocation. + static const QString pyObject = PYTHON_RETURN_VAR + u".object()"_s; + qsizetype pos{}; + while ( (pos = code->indexOf(pyArg0)) >= 0) { + const auto next = pos + pyArg0.size(); + const bool memberInvocation = next < code->size() && code->at(next) == u'.'; + code->replace(pos, pyArg0.size(), + memberInvocation ? PYTHON_RETURN_VAR : pyObject); + } +} + +void ShibokenGenerator::writeCodeSnips(TextStream &s, + const CodeSnipList &codeSnips, + TypeSystem::CodeSnipPosition position, + TypeSystem::Language language, + const AbstractMetaFunctionCPtr &func, + bool usePyArgs, + const AbstractMetaArgument *lastArg) const +{ + QString code = getCodeSnippets(codeSnips, position, language); + if (code.isEmpty()) + return; + + // Replace %PYARG_# variables. + replacePyArg0(language, &code); + + static const QRegularExpression pyArgsRegex("%PYARG_(\\d+)"_L1); + Q_ASSERT(pyArgsRegex.isValid()); + if (language == TypeSystem::TargetLangCode) { + if (usePyArgs) { + code.replace(pyArgsRegex, PYTHON_ARGS + u"[\\1-1]"_s); + } else { + static const QRegularExpression pyArgsRegexCheck("%PYARG_([2-9]+)"_L1); + Q_ASSERT(pyArgsRegexCheck.isValid()); + const QRegularExpressionMatch match = pyArgsRegexCheck.match(code); + if (match.hasMatch()) { + qCWarning(lcShiboken).noquote().nospace() + << msgWrongIndex("%PYARG", match.captured(1), func.get()); + return; + } + code.replace(u"%PYARG_1"_s, PYTHON_ARG); + } + } else { + // Replaces the simplest case of attribution to a + // Python argument on the binding virtual method. + static const QRegularExpression pyArgsAttributionRegex("%PYARG_(\\d+)\\s*=[^=]\\s*([^;]+)"_L1); + Q_ASSERT(pyArgsAttributionRegex.isValid()); + code.replace(pyArgsAttributionRegex, u"PyTuple_SET_ITEM("_s + + PYTHON_ARGS + u".object(), \\1-1, \\2)"_s); + code.replace(pyArgsRegex, u"PyTuple_GET_ITEM("_s + + PYTHON_ARGS + u".object(), \\1-1)"_s); + } + + // Replace %ARG#_TYPE variables. + const AbstractMetaArgumentList &arguments = func->arguments(); + for (const AbstractMetaArgument &arg : arguments) { + QString argTypeVar = u"%ARG"_s + QString::number(arg.argumentIndex() + 1) + + u"_TYPE"_s; + QString argTypeVal = arg.type().cppSignature(); + code.replace(argTypeVar, argTypeVal); + } + + static const QRegularExpression cppArgTypeRegexCheck("%ARG(\\d+)_TYPE"_L1); + Q_ASSERT(cppArgTypeRegexCheck.isValid()); + QRegularExpressionMatchIterator rit = cppArgTypeRegexCheck.globalMatch(code); + while (rit.hasNext()) { + QRegularExpressionMatch match = rit.next(); + qCWarning(lcShiboken).noquote().nospace() + << msgWrongIndex("%ARG#_TYPE", match.captured(1), func.get()); + } + + // Replace template variable for return variable name. + if (func->isConstructor()) { + code.replace(u"%0."_s, u"cptr->"_s); + code.replace(u"%0"_s, u"cptr"_s); + } else if (!func->isVoid()) { + QString returnValueOp = func->type().isPointerToWrapperType() + ? u"%1->"_s : u"%1."_s; + if (func->type().isWrapperType()) + code.replace(u"%0."_s, returnValueOp.arg(CPP_RETURN_VAR)); + code.replace(u"%0"_s, CPP_RETURN_VAR); + } + + // Replace template variable for self Python object. + QString pySelf = language == TypeSystem::NativeCode + ? u"pySelf"_s : u"self"_s; + code.replace(u"%PYSELF"_s, pySelf); + + // Replace template variable for a pointer to C++ of this object. + if (func->implementingClass()) { + QString replacement = func->isStatic() ? u"%1::"_s : u"%1->"_s; + QString cppSelf; + if (func->isStatic()) + cppSelf = func->ownerClass()->qualifiedCppName(); + else if (language == TypeSystem::NativeCode) + cppSelf = u"this"_s; + else + cppSelf = CPP_SELF_VAR; + + // On comparison operator CPP_SELF_VAR is always a reference. + if (func->isComparisonOperator()) + replacement = u"%1."_s; + + if (func->isVirtual() && !func->isAbstract() && (!avoidProtectedHack() || !func->isProtected())) { + QString methodCallArgs = getArgumentsFromMethodCall(code); + if (!methodCallArgs.isEmpty()) { + const QString pattern = u"%CPPSELF.%FUNCTION_NAME("_s + methodCallArgs + u')'; + QString replacement = u"(Shiboken::Object::hasCppWrapper(reinterpret_cast<SbkObject *>("_s + + pySelf + u")) ? "_s; + if (func->name() == u"metaObject") { + QString wrapperClassName = wrapperName(func->ownerClass()); + QString cppSelfVar = avoidProtectedHack() + ? u"%CPPSELF"_s + : u"reinterpret_cast<"_s + wrapperClassName + u" *>(%CPPSELF)"_s; + replacement += cppSelfVar + u"->::"_s + wrapperClassName + + u"::%FUNCTION_NAME("_s + methodCallArgs + + u") : %CPPSELF.%FUNCTION_NAME("_s + methodCallArgs + u"))"_s; + } else { + replacement += u"%CPPSELF->::%TYPE::%FUNCTION_NAME("_s + methodCallArgs + + u") : %CPPSELF.%FUNCTION_NAME("_s + methodCallArgs + u"))"_s; + } + code.replace(pattern, replacement); + } + } + + code.replace(u"%CPPSELF."_s, replacement.arg(cppSelf)); + code.replace(u"%CPPSELF"_s, cppSelf); + + if (code.indexOf(u"%BEGIN_ALLOW_THREADS") > -1) { + if (code.count(u"%BEGIN_ALLOW_THREADS"_s) == code.count(u"%END_ALLOW_THREADS"_s)) { + code.replace(u"%BEGIN_ALLOW_THREADS"_s, BEGIN_ALLOW_THREADS); + code.replace(u"%END_ALLOW_THREADS"_s, END_ALLOW_THREADS); + } else { + qCWarning(lcShiboken) << "%BEGIN_ALLOW_THREADS and %END_ALLOW_THREADS mismatch"; + } + } + + // replace template variable for the Python Type object for the + // class implementing the method in which the code snip is written + if (func->isStatic()) { + code.replace(u"%PYTHONTYPEOBJECT"_s, + u"(*"_s + cpythonTypeName(func->implementingClass()) + u')'); + } else { + code.replace(u"%PYTHONTYPEOBJECT."_s, pySelf + u"->ob_type->"_s); + code.replace(u"%PYTHONTYPEOBJECT"_s, pySelf + u"->ob_type"_s); + } + } + + // Replaces template %ARGUMENT_NAMES and %# variables by argument variables and values. + // Replaces template variables %# for individual arguments. + const ArgumentVarReplacementList &argReplacements = getArgumentReplacement(func, usePyArgs, language, lastArg); + + QStringList args; + for (const ArgumentVarReplacementPair &pair : argReplacements) { + if (pair.second.startsWith(CPP_ARG_REMOVED_PREFIX)) + continue; + args << pair.second; + } + code.replace(u"%ARGUMENT_NAMES"_s, args.join(u", "_s)); + + for (const ArgumentVarReplacementPair &pair : argReplacements) { + const AbstractMetaArgument &arg = pair.first; + int idx = arg.argumentIndex() + 1; + AbstractMetaType type = arg.modifiedType(); + if (type.isWrapperType()) { + QString replacement = pair.second; + const auto generatorArg = GeneratorArgument::fromMetaType(type); + if (generatorArg.indirections > 0) + AbstractMetaType::stripDereference(&replacement); + if (type.referenceType() == LValueReference || type.isPointer()) + code.replace(u'%' + QString::number(idx) + u'.', replacement + u"->"_s); + } + code.replace(CodeSnipAbstract::placeHolderRegex(idx), pair.second); + } + + if (language == TypeSystem::NativeCode) { + // Replaces template %PYTHON_ARGUMENTS variable with a pointer to the Python tuple + // containing the converted virtual method arguments received from C++ to be passed + // to the Python override. + code.replace(u"%PYTHON_ARGUMENTS"_s, PYTHON_ARGS); + + // replace variable %PYTHON_METHOD_OVERRIDE for a pointer to the Python method + // override for the C++ virtual method in which this piece of code was inserted + code.replace(u"%PYTHON_METHOD_OVERRIDE"_s, PYTHON_OVERRIDE_VAR); + } + + if (avoidProtectedHack()) { + // If the function being processed was added by the user via type system, + // Shiboken needs to find out if there are other overloads for the same method + // name and if any of them is of the protected visibility. This is used to replace + // calls to %FUNCTION_NAME on user written custom code for calls to the protected + // dispatcher. + bool isProtected = func->isProtected(); + auto owner = func->ownerClass(); + if (!isProtected && func->isUserAdded() && owner != nullptr) { + const auto &funcs = getFunctionGroups(owner).value(func->name()); + isProtected = std::any_of(funcs.cbegin(), funcs.cend(), + [](const AbstractMetaFunctionCPtr &f) { + return f->isProtected(); + }); + } + + if (isProtected) { + code.replace(u"%TYPE::%FUNCTION_NAME"_s, + wrapperName(func->ownerClass()) + "::"_L1 + + func->originalName() + "_protected"_L1); + code.replace(u"%FUNCTION_NAME"_s, + func->originalName() + u"_protected"_s); + } + } + + if (func->isConstructor() && shouldGenerateCppWrapper(func->ownerClass())) + code.replace(u"%TYPE"_s, wrapperName(func->ownerClass())); + + if (func->ownerClass()) + code.replace(u"%CPPTYPE"_s, func->ownerClass()->name()); + + replaceTemplateVariables(code, func); + + processCodeSnip(code, func->classQualifiedSignature()); + s << "// Begin code injection\n" << code << "// End of code injection\n\n"; +} + +// Returns true if the string is an expression, +// and false if it is a variable. +static bool isVariable(const QString &code) +{ + static const QRegularExpression expr("^\\s*\\*?\\s*[A-Za-z_][A-Za-z_0-9.]*\\s*(?:\\[[^\\[]+\\])*$"_L1); + Q_ASSERT(expr.isValid()); + return expr.match(code.trimmed()).hasMatch(); +} + +// A miniature normalizer that puts a type string into a format +// suitable for comparison with AbstractMetaType::cppSignature() +// result. +static QString miniNormalizer(const QString &varType) +{ + QString normalized = varType.trimmed(); + if (normalized.isEmpty()) + return normalized; + if (normalized.startsWith(u"::")) + normalized.remove(0, 2); + QString suffix; + while (normalized.endsWith(u'*') || normalized.endsWith(u'&')) { + suffix.prepend(normalized.at(normalized.size() - 1)); + normalized.chop(1); + normalized = normalized.trimmed(); + } + const QString result = normalized + u' ' + suffix; + return result.trimmed(); +} +// The position must indicate the first character after the opening '('. +// ATTENTION: do not modify this function to trim any resulting string! +// This must be done elsewhere. +static QString getConverterTypeSystemVariableArgument(const QString &code, int pos) +{ + QString arg; + int parenthesisDepth = 0; + int count = 0; + while (pos + count < code.size()) { + char c = code.at(pos+count).toLatin1(); // toAscii is gone + if (c == '(') { + ++parenthesisDepth; + } else if (c == ')') { + if (parenthesisDepth == 0) { + arg = code.mid(pos, count).trimmed(); + break; + } + --parenthesisDepth; + } + ++count; + } + if (parenthesisDepth != 0) + throw Exception("Unbalanced parenthesis on type system converter variable call."); + return arg; +} + +const QHash<int, QString> &ShibokenGenerator::typeSystemConvName() +{ + static const QHash<int, QString> result = { + {TypeSystemCheckFunction, u"checkType"_s}, + {TypeSystemIsConvertibleFunction, u"isConvertible"_s}, + {TypeSystemToCppFunction, u"toCpp"_s}, + {TypeSystemToPythonFunction, u"toPython"_s} + }; + return result; +} + +using StringPair = std::pair<QString, QString>; + +void ShibokenGenerator::replaceConverterTypeSystemVariable(TypeSystemConverterVariable converterVariable, + QString &code) const +{ + QList<StringPair> replacements; + QRegularExpressionMatchIterator rit = typeSystemConvRegExps()[converterVariable].globalMatch(code); + while (rit.hasNext()) { + const QRegularExpressionMatch match = rit.next(); + const QStringList list = match.capturedTexts(); + QString conversionString = list.constFirst(); + const QString &conversionTypeName = list.constLast(); + QString message; + const auto conversionTypeO = AbstractMetaType::fromString(conversionTypeName, &message); + if (!conversionTypeO.has_value()) { + throw Exception(msgCannotFindType(conversionTypeName, + typeSystemConvName().value(converterVariable), + message)); + } + const auto conversionType = conversionTypeO.value(); + QString conversion; + switch (converterVariable) { + case TypeSystemToCppFunction: { + StringStream c(TextStream::Language::Cpp); + int end = match.capturedStart(); + int start = end; + while (start > 0 && code.at(start) != u'\n') + --start; + while (code.at(start).isSpace()) + ++start; + QString varType = code.mid(start, end - start); + conversionString = varType + list.constFirst(); + varType = miniNormalizer(varType); + QString varName = list.at(1).trimmed(); + if (!varType.isEmpty()) { + c << getFullTypeName(conversionType) << ' ' << varName + << minimalConstructorExpression(api(), conversionType) << ";\n"; + } + c << cpythonToCppConversionFunction(conversionType); + QString prefix; + if (!AbstractMetaType::stripDereference(&varName)) + prefix = u'&'; + QString arg = getConverterTypeSystemVariableArgument(code, match.capturedEnd()); + conversionString += arg; + c << arg << ", " << prefix << '(' << varName << ')'; + conversion = c.toString(); + break; + } + case TypeSystemCheckFunction: + conversion = cpythonCheckFunction(conversionType); + if (conversionType.typeEntry()->isPrimitive() + && (conversionType.typeEntry()->name() == cPyObjectT + || !conversion.endsWith(u' '))) { + conversion += u'('; + break; + } + Q_FALLTHROUGH(); + case TypeSystemIsConvertibleFunction: + if (conversion.isEmpty()) + conversion = cpythonIsConvertibleFunction(conversionType); + Q_FALLTHROUGH(); + case TypeSystemToPythonFunction: + if (conversion.isEmpty()) + conversion = cpythonToPythonConversionFunction(conversionType); + Q_FALLTHROUGH(); + default: { + QString arg = getConverterTypeSystemVariableArgument(code, match.capturedEnd()); + conversionString += arg; + if (converterVariable == TypeSystemToPythonFunction && !isVariable(arg)) { + QString m; + QTextStream(&m) << "Only variables are acceptable as argument to %%CONVERTTOPYTHON type system variable on code snippet: '" + << code << '\''; + throw Exception(m); + } + if (conversion.contains(u"%in")) { + conversion.prepend(u'('); + conversion.replace(u"%in"_s, arg); + } else { + conversion += arg; + } + } + } + replacements.append(std::make_pair(conversionString, conversion)); + } + for (const StringPair &rep : std::as_const(replacements)) + code.replace(rep.first, rep.second); +} + +bool ShibokenGenerator::injectedCodeCallsCppFunction(const GeneratorContext &context, + const AbstractMetaFunctionCPtr &func) +{ + if (func->injectedCodeContains(u"%FUNCTION_NAME(")) + return true; + QString funcCall = func->originalName() + u'('; + if (func->isConstructor()) + funcCall.prepend(u"new "_s); + if (func->injectedCodeContains(funcCall)) + return true; + if (!func->isConstructor()) + return false; + if (func->injectedCodeContains(u"new %TYPE(")) + return true; + const auto owner = func->ownerClass(); + if (!owner->isPolymorphic()) + return false; + const QString wrappedCtorCall = u"new "_s + context.effectiveClassName() + u'('; + return func->injectedCodeContains(wrappedCtorCall); +} + +bool ShibokenGenerator::useOverrideCaching(const AbstractMetaClassCPtr &metaClass) +{ + return metaClass->isPolymorphic(); +} + +ShibokenGenerator::AttroCheck ShibokenGenerator::checkAttroFunctionNeeds( + const AbstractMetaClassCPtr &metaClass) +{ + AttroCheck result; + if (metaClass->typeEntry()->isSmartPointer()) { + result |= AttroCheckFlag::GetattroSmartPointer | AttroCheckFlag::SetattroSmartPointer; + } else { + if (getGeneratorClassInfo(metaClass).needsGetattroFunction) + result |= AttroCheckFlag::GetattroOverloads; + if (metaClass->queryFirstFunction(metaClass->functions(), + FunctionQueryOption::GetAttroFunction)) { + result |= AttroCheckFlag::GetattroUser; + } + if (usePySideExtensions() && metaClass->qualifiedCppName() == qObjectT) + result |= AttroCheckFlag::SetattroQObject; + if (useOverrideCaching(metaClass)) + result |= AttroCheckFlag::SetattroMethodOverride; + if (metaClass->queryFirstFunction(metaClass->functions(), + FunctionQueryOption::SetAttroFunction)) { + result |= AttroCheckFlag::SetattroUser; + } + // PYSIDE-1255: If setattro is generated for a class inheriting + // QObject, the property code needs to be generated, too. + if ((result & AttroCheckFlag::SetattroMask) != 0 + && !result.testFlag(AttroCheckFlag::SetattroQObject) + && isQObject(metaClass)) { + result |= AttroCheckFlag::SetattroQObject; + } + } + return result; +} + +bool ShibokenGenerator::classNeedsGetattroFunctionImpl(const AbstractMetaClassCPtr &metaClass) +{ + if (!metaClass) + return false; + if (metaClass->typeEntry()->isSmartPointer()) + return true; + const auto &functionGroup = getFunctionGroups(metaClass); + for (auto it = functionGroup.cbegin(), end = functionGroup.cend(); it != end; ++it) { + AbstractMetaFunctionCList overloads; + for (const auto &func : std::as_const(it.value())) { + if (func->isAssignmentOperator() || func->isConversionOperator() + || func->isModifiedRemoved() + || func->isPrivate() || func->ownerClass() != func->implementingClass() + || func->isConstructor() || func->isOperatorOverload()) + continue; + overloads.append(func); + } + if (overloads.isEmpty()) + continue; + if (OverloadData::hasStaticAndInstanceFunctions(overloads)) + return true; + } + return false; +} + +AbstractMetaFunctionCList + ShibokenGenerator::getMethodsWithBothStaticAndNonStaticMethods(const AbstractMetaClassCPtr &metaClass) +{ + AbstractMetaFunctionCList methods; + if (metaClass) { + const auto &functionGroups = getFunctionGroups(metaClass); + for (auto it = functionGroups.cbegin(), end = functionGroups.cend(); it != end; ++it) { + AbstractMetaFunctionCList overloads; + for (const auto &func : std::as_const(it.value())) { + if (func->isAssignmentOperator() || func->isConversionOperator() + || func->isModifiedRemoved() + || func->isPrivate() || func->ownerClass() != func->implementingClass() + || func->isConstructor() || func->isOperatorOverload()) + continue; + overloads.append(func); + } + if (overloads.isEmpty()) + continue; + if (OverloadData::hasStaticAndInstanceFunctions(overloads)) + methods.append(overloads.constFirst()); + } + } + return methods; +} + +AbstractMetaClassCPtr + ShibokenGenerator::getMultipleInheritingClass(const AbstractMetaClassCPtr &metaClass) +{ + if (!metaClass || metaClass->baseClassNames().isEmpty()) + return nullptr; + if (metaClass->baseClassNames().size() > 1) + return metaClass; + return getMultipleInheritingClass(metaClass->baseClass()); +} + +QString ShibokenGenerator::getModuleHeaderFileBaseName(const QString &moduleName) +{ + return moduleCppPrefix(moduleName).toLower() + "_python"_L1; +} + +QString ShibokenGenerator::getModuleHeaderFileName(const QString &moduleName) +{ + return getModuleHeaderFileBaseName(moduleName) + ".h"_L1; +} + +QString ShibokenGenerator::getPrivateModuleHeaderFileName(const QString &moduleName) +{ + return getModuleHeaderFileBaseName(moduleName) + "_p.h"_L1; +} + +IncludeGroupList ShibokenGenerator::classIncludes(const AbstractMetaClassCPtr &metaClass) const +{ + IncludeGroupList result; + const auto typeEntry = metaClass->typeEntry(); + //Extra includes + result.append(IncludeGroup{u"Extra includes"_s, + typeEntry->extraIncludes()}); + + result.append({u"Enum includes"_s, {}}); + for (const auto &cppEnum : metaClass->enums()) + result.back().includes.append(cppEnum.typeEntry()->extraIncludes()); + + result.append({u"Argument includes"_s, typeEntry->argumentIncludes()}); + const auto implicitConvs = implicitConversions(typeEntry); + for (const auto &f : implicitConvs) { + if (f->isConversionOperator()) { + const auto source = f->ownerClass(); + Q_ASSERT(source); + result.back().append(source->typeEntry()->include()); + } + } + return result; +} + +/* +static void dumpFunction(AbstractMetaFunctionList lst) +{ + qDebug() << "DUMP FUNCTIONS: "; + for (AbstractMetaFunction *func : std::as_const(lst)) + qDebug() << "*" << func->ownerClass()->name() + << func->signature() + << "Private: " << func->isPrivate() + << "Empty: " << func->isEmptyFunction() + << "Static:" << func->isStatic() + << "Signal:" << func->isSignal() + << "ClassImplements: " << (func->ownerClass() != func->implementingClass()) + << "is operator:" << func->isOperatorOverload() + << "is global:" << func->isInGlobalScope(); +} +*/ + +static bool isGroupable(const AbstractMetaFunctionCPtr &func) +{ + switch (func->functionType()) { + case AbstractMetaFunction::DestructorFunction: + case AbstractMetaFunction::SignalFunction: + case AbstractMetaFunction::GetAttroFunction: + case AbstractMetaFunction::SetAttroFunction: + case AbstractMetaFunction::ArrowOperator: // weird operator overloads + case AbstractMetaFunction::SubscriptOperator: + return false; + default: + break; + } + if (func->isModifiedRemoved() && !func->isAbstract()) + return false; + return true; +} + +static void insertIntoFunctionGroups(const AbstractMetaFunctionCList &lst, + ShibokenGenerator::FunctionGroups *results) +{ + for (const auto &func : lst) { + if (isGroupable(func)) + (*results)[func->name()].append(func); + } +} + +ShibokenGenerator::FunctionGroups ShibokenGenerator::getGlobalFunctionGroups() const +{ + FunctionGroups results; + insertIntoFunctionGroups(api().globalFunctions(), &results); + for (const auto &nsp : invisibleTopNamespaces()) + insertIntoFunctionGroups(nsp->functions(), &results); + return results; +} + +const GeneratorClassInfoCacheEntry & + ShibokenGenerator::getGeneratorClassInfo(const AbstractMetaClassCPtr &scope) +{ + auto cache = generatorClassInfoCache(); + auto it = cache->find(scope); + if (it == cache->end()) { + it = cache->insert(scope, {}); + auto &entry = it.value(); + entry.functionGroups = getFunctionGroupsImpl(scope); + entry.needsGetattroFunction = classNeedsGetattroFunctionImpl(scope); + entry.numberProtocolOperators = getNumberProtocolOperators(scope); + entry.boolCastFunctionO = getBoolCast(scope); + } + return it.value(); +} + +ShibokenGenerator::FunctionGroups + ShibokenGenerator::getFunctionGroups(const AbstractMetaClassCPtr &scope) +{ + Q_ASSERT(scope); + return getGeneratorClassInfo(scope).functionGroups; +} + +QList<AbstractMetaFunctionCList> + ShibokenGenerator::numberProtocolOperators(const AbstractMetaClassCPtr &scope) +{ + Q_ASSERT(scope); + return getGeneratorClassInfo(scope).numberProtocolOperators; +} + +BoolCastFunctionOptional ShibokenGenerator::boolCast(const AbstractMetaClassCPtr &scope) +{ + Q_ASSERT(scope); + return getGeneratorClassInfo(scope).boolCastFunctionO; +} + +// Use non-const overloads only, for example, "foo()" and "foo()const" +// the second is removed. +static void removeConstOverloads(AbstractMetaFunctionCList *overloads) +{ + for (qsizetype i = overloads->size() - 1; i >= 0; --i) { + const auto &f = overloads->at(i); + if (f->isConstant()) { + for (qsizetype c = 0, size = overloads->size(); c < size; ++c) { + if (f->isConstOverloadOf(overloads->at(c).get())) { + overloads->removeAt(i); + break; + } + } + } + } +} + +ShibokenGenerator::FunctionGroups + ShibokenGenerator::getFunctionGroupsImpl(const AbstractMetaClassCPtr &scope) +{ + AbstractMetaFunctionCList lst = scope->functions(); + scope->getFunctionsFromInvisibleNamespacesToBeGenerated(&lst); + + FunctionGroups results; + for (const auto &func : lst) { + if (isGroupable(func) + && func->ownerClass() == func->implementingClass() + && func->generateBinding()) { + auto it = results.find(func->name()); + if (it == results.end()) { + it = results.insert(func->name(), AbstractMetaFunctionCList(1, func)); + } else { + // If there are virtuals methods in the mix (PYSIDE-570, + // QFileSystemModel::index(QString,int) and + // QFileSystemModel::index(int,int,QModelIndex)) override, make sure + // the overriding method of the most-derived class is seen first + // and inserted into the "seenSignatures" set. + if (func->isVirtual()) + it.value().prepend(func); + else + it.value().append(func); + } + getInheritedOverloads(scope, &it.value()); + removeConstOverloads(&it.value()); + } + } + return results; +} + +static bool removeNumberProtocolOperator(const AbstractMetaFunctionCPtr &f) +{ + return !f->generateBinding() + || (f->ownerClass() != f->implementingClass() && !f->isAbstract()); +} + +QList<AbstractMetaFunctionCList> + ShibokenGenerator::getNumberProtocolOperators(const AbstractMetaClassCPtr &metaClass) +{ + QList<AbstractMetaFunctionCList> result; + if (metaClass->isNamespace()) + return result; + result = filterGroupedOperatorFunctions( + metaClass, + OperatorQueryOption::ArithmeticOp + | OperatorQueryOption::IncDecrementOp + | OperatorQueryOption::LogicalOp + | OperatorQueryOption::BitwiseOp + | OperatorQueryOption::ConversionOp); + + for (auto i = result.size() - 1; i >= 0; --i) { + AbstractMetaFunctionCList &l = result[i]; + auto rend = std::remove_if(l.begin(), l.end(), removeNumberProtocolOperator); + l.erase(rend, l.end()); + if (l.isEmpty()) + result.removeAt(i); + } + + return result; +} + +BoolCastFunctionOptional +ShibokenGenerator::getBoolCast(const AbstractMetaClassCPtr &metaClass) +{ + if (metaClass->isNamespace()) + return std::nullopt; + + const auto te = metaClass->typeEntry(); + if (te->isSmartPointer()) { + auto ste = std::static_pointer_cast<const SmartPointerTypeEntry>(te); + + auto valueCheckMethod = ste->valueCheckMethod(); + if (!valueCheckMethod.isEmpty()) { + const auto func = metaClass->findFunction(valueCheckMethod); + if (!func) + throw Exception(msgMethodNotFound(metaClass, valueCheckMethod)); + return BoolCastFunction{func, false}; + } + + auto nullCheckMethod = ste->nullCheckMethod(); + if (!nullCheckMethod.isEmpty()) { + const auto func = metaClass->findFunction(nullCheckMethod); + if (!func) + throw Exception(msgMethodNotFound(metaClass, nullCheckMethod)); + return BoolCastFunction{func, true}; + } + } + + auto mode = te->operatorBoolMode(); + if (useOperatorBoolAsNbBool() + ? mode != TypeSystem::BoolCast::Disabled : mode == TypeSystem::BoolCast::Enabled) { + const auto func = metaClass->findOperatorBool(); + if (func) + return BoolCastFunction{func, false}; + } + + mode = te->isNullMode(); + if (useIsNullAsNbBool() + ? mode != TypeSystem::BoolCast::Disabled : mode == TypeSystem::BoolCast::Enabled) { + const auto func = metaClass->findQtIsNullMethod(); + if (func) + return BoolCastFunction{func, true}; + } + return std::nullopt; +} + +static bool isInplaceAdd(const AbstractMetaFunctionCPtr &func) +{ + return func->name() == u"operator+="; +} + +static bool isIncrementOperator(const AbstractMetaFunctionCPtr &func) +{ + return func->functionType() == AbstractMetaFunction::IncrementOperator; +} + +static bool isDecrementOperator(const AbstractMetaFunctionCPtr &func) +{ + return func->functionType() == AbstractMetaFunction::DecrementOperator; +} + +// Filter predicate for operator functions +static bool skipOperatorFunc(const AbstractMetaFunctionCPtr &func) +{ + if (func->isModifiedRemoved() || func->usesRValueReferences()) + return true; + const auto &name = func->name(); + return name == u"operator[]" || name == u"operator->" || name == u"operator!" + || name == u"operator/="; // __idiv__ is not needed in Python3 +} + +QList<AbstractMetaFunctionCList> +ShibokenGenerator::filterGroupedOperatorFunctions(const AbstractMetaClassCPtr &metaClass, + OperatorQueryOptions query) +{ + // ( func_name, num_args ) => func_list + QMap<std::pair<QString, int>, AbstractMetaFunctionCList> results; + auto funcs = metaClass->operatorOverloads(query); + auto end = std::remove_if(funcs.begin(), funcs.end(), skipOperatorFunc); + funcs.erase(end, funcs.end()); + // If we have operator+=, we remove the operator++/-- which would + // otherwise be used for emulating __iadd__, __isub__. + if (std::any_of(funcs.cbegin(), funcs.cend(), isInplaceAdd)) { + end = std::remove_if(funcs.begin(), funcs.end(), + [] (const AbstractMetaFunctionCPtr &func) { + return func->isIncDecrementOperator(); + }); + funcs.erase(end, funcs.end()); + } else { + // If both prefix/postfix ++/-- are present, remove one + if (std::count_if(funcs.begin(), funcs.end(), isIncrementOperator) > 1) + funcs.erase(std::find_if(funcs.begin(), funcs.end(), isIncrementOperator)); + if (std::count_if(funcs.begin(), funcs.end(), isDecrementOperator) > 1) + funcs.erase(std::find_if(funcs.begin(), funcs.end(), isDecrementOperator)); + } + for (const auto &func : funcs) { + int args; + if (func->isComparisonOperator()) { + args = -1; + } else { + args = func->arguments().size(); + } + auto op = std::make_pair(func->name(), args); + results[op].append(func); + } + QList<AbstractMetaFunctionCList> result; + result.reserve(results.size()); + for (auto it = results.cbegin(), end = results.cend(); it != end; ++it) + result.append(it.value()); + return result; +} + +static bool hidesBaseClassFunctions(const AbstractMetaFunctionCPtr &f) +{ + auto attributes = f->cppAttributes(); + return !attributes.testFlag(FunctionAttribute::Override) + && !attributes.testFlag(FunctionAttribute::Final); +} + +void ShibokenGenerator::getInheritedOverloads(const AbstractMetaClassCPtr &scope, + AbstractMetaFunctionCList *overloads) +{ + if (overloads->isEmpty() || scope->isNamespace() || scope->baseClasses().isEmpty()) + return; + + // PYSIDE-331: look also into base classes. Check for any non-overriding + // function hiding the base class functions. + const bool hideBaseClassFunctions = + std::any_of(overloads->cbegin(), overloads->cend(), hidesBaseClassFunctions); + + const QString &functionName = overloads->constFirst()->name(); + const bool hasUsingDeclarations = scope->hasUsingMemberFor(functionName); + if (hideBaseClassFunctions && !hasUsingDeclarations) + return; // No base function is visible + + // Collect base candidates by name and signature + bool staticEncountered = false; + QSet<QString> seenSignatures; + for (const auto &func : *overloads) { + seenSignatures.insert(func->minimalSignature()); + staticEncountered |= func->isStatic(); + } + + AbstractMetaFunctionCList baseCandidates; + + auto basePredicate = [&functionName, &seenSignatures, &baseCandidates] + (const AbstractMetaClassCPtr &b) { + for (const auto &f : b->functions()) { + if (f->generateBinding() && f->name() == functionName) { + const QString signature = f->minimalSignature(); + if (!seenSignatures.contains(signature)) { + seenSignatures.insert(signature); + baseCandidates.append(f); + } + } + } + return false; // Keep going + }; + + for (const auto &baseClass : scope->baseClasses()) + recurseClassHierarchy(baseClass, basePredicate); + + // Remove the ones that are not made visible with using declarations + if (hideBaseClassFunctions && hasUsingDeclarations) { + const auto pred = [scope](const AbstractMetaFunctionCPtr &f) { + return !scope->isUsingMember(f->ownerClass(), f->name(), f->access()); + }; + auto end = std::remove_if(baseCandidates.begin(), baseCandidates.end(), pred); + baseCandidates.erase(end, baseCandidates.end()); + } + + // PYSIDE-886: If the method does not have any static overloads declared + // in the class in question, remove all inherited static methods as setting + // METH_STATIC in that case can cause crashes for the instance methods. + // Manifested as crash when calling QPlainTextEdit::find() (clash with + // static QWidget::find(WId)). + if (!staticEncountered) { + auto end = std::remove_if(baseCandidates.begin(), baseCandidates.end(), + [](const AbstractMetaFunctionCPtr &f) { return f->isStatic(); }); + baseCandidates.erase(end, baseCandidates.end()); + } + + for (const auto &baseCandidate : baseCandidates) { + AbstractMetaFunction *newFunc = baseCandidate->copy(); + newFunc->setImplementingClass(scope); + overloads->append(AbstractMetaFunctionCPtr(newFunc)); + } +} + +QList<OptionDescription> ShibokenGenerator::options() +{ + return { + {DISABLE_VERBOSE_ERROR_MESSAGES, + u"Disable verbose error messages. Turn the python code hard to debug\n" + "but safe few kB on the generated bindings."_s}, + {PARENT_CTOR_HEURISTIC, + u"Enable heuristics to detect parent relationship on constructors."_s}, + {RETURN_VALUE_HEURISTIC, + u"Enable heuristics to detect parent relationship on return values\n" + "(USE WITH CAUTION!)"_s}, + {USE_ISNULL_AS_NB_BOOL, + u"If a class have an isNull() const method, it will be used to compute\n" + "the value of boolean casts"_s}, + {LEAN_HEADERS, + u"Forward declare classes in module headers"_s}, + {USE_OPERATOR_BOOL_AS_NB_BOOL, + u"If a class has an operator bool, it will be used to compute\n" + "the value of boolean casts"_s}, + {NO_IMPLICIT_CONVERSIONS, + u"Do not generate implicit_conversions for function arguments."_s}, + {WRAPPER_DIAGNOSTICS, + u"Generate diagnostic code around wrappers"_s} + }; +} + +class ShibokenGeneratorOptionsParser : public OptionsParser +{ +public: + explicit ShibokenGeneratorOptionsParser(ShibokenGeneratorOptions *o) : m_options(o) {} + + bool handleBoolOption(const QString & key, OptionSource source) override; + +private: + ShibokenGeneratorOptions *m_options; +}; + +bool ShibokenGeneratorOptionsParser::handleBoolOption(const QString &key, OptionSource source) +{ + if (source == OptionSource::CommandLineSingleDash) + return false; + if (key == PARENT_CTOR_HEURISTIC) + return (m_options->useCtorHeuristic = true); + if (key == RETURN_VALUE_HEURISTIC) + return (m_options->userReturnValueHeuristic = true); + if (key == DISABLE_VERBOSE_ERROR_MESSAGES) + return (m_options->verboseErrorMessagesDisabled = true); + if (key == USE_ISNULL_AS_NB_BOOL || key == USE_ISNULL_AS_NB_NONZERO) { + return (m_options->useIsNullAsNbBool = true); + } + if (key == LEAN_HEADERS) + return (m_options->leanHeaders= true); + if (key == USE_OPERATOR_BOOL_AS_NB_BOOL || key == USE_OPERATOR_BOOL_AS_NB_NONZERO) { + return (m_options->useOperatorBoolAsNbBool = true); + } + if (key == NO_IMPLICIT_CONVERSIONS) { + m_options->generateImplicitConversions = false; + return true; + } + if (key == WRAPPER_DIAGNOSTICS) + return (m_options->wrapperDiagnostics = true); + return false; +} + +std::shared_ptr<OptionsParser> ShibokenGenerator::createOptionsParser() +{ + return std::make_shared<ShibokenGeneratorOptionsParser>(&m_options); +} + +bool ShibokenGenerator::doSetup() +{ + return true; +} + +bool ShibokenGenerator::useCtorHeuristic() +{ + return m_options.useCtorHeuristic; +} + +bool ShibokenGenerator::useReturnValueHeuristic() +{ + return m_options.userReturnValueHeuristic; +} + +bool ShibokenGenerator::useIsNullAsNbBool() +{ + return m_options.useIsNullAsNbBool; +} + +bool ShibokenGenerator::leanHeaders() +{ + return m_options.leanHeaders; +} + +bool ShibokenGenerator::useOperatorBoolAsNbBool() +{ + return m_options.useOperatorBoolAsNbBool; +} + +bool ShibokenGenerator::generateImplicitConversions() +{ + return m_options.generateImplicitConversions; +} + +QString ShibokenGenerator::moduleCppPrefix(const QString &moduleName) + { + QString result = moduleName.isEmpty() ? packageName() : moduleName; + result.replace(u'.', u'_'); + return result; +} + +QString ShibokenGenerator::cppApiVariableNameOld(const QString &moduleName) +{ + return "Sbk"_L1 + moduleCppPrefix(moduleName) + "Types"_L1; +} + +QString ShibokenGenerator::cppApiVariableName(const QString &moduleName) +{ + return "Sbk"_L1 + moduleCppPrefix(moduleName) + "TypeStructs"_L1; +} + +QString ShibokenGenerator::pythonModuleObjectName(const QString &moduleName) +{ + return "Sbk"_L1 + moduleCppPrefix(moduleName) + "ModuleObject"_L1; +} + +QString ShibokenGenerator::convertersVariableName(const QString &moduleName) +{ + QString result = cppApiVariableNameOld(moduleName); + result.chop(1); + result.append(u"Converters"_s); + return result; +} + +static QString processInstantiationsVariableName(const AbstractMetaType &type) +{ + QString res = u'_' + _fixedCppTypeName(type.typeEntry()->qualifiedCppName()); + for (const auto &instantiation : type.instantiations()) { + res += instantiation.isContainer() + ? processInstantiationsVariableName(instantiation) + : u'_' + _fixedCppTypeName(instantiation.cppSignature()); + } + return res; +} + +static void appendIndexSuffix(QString *s) +{ + if (!s->endsWith(u'_')) + s->append(u'_'); + s->append("IDX"_L1); +} + +QString + ShibokenGenerator::getTypeAlternateTemplateIndexVariableName(const AbstractMetaClassCPtr &metaClass) +{ + const auto templateBaseClass = metaClass->templateBaseClass(); + Q_ASSERT(templateBaseClass); + QString result = u"SBK_"_s + + _fixedCppTypeName(templateBaseClass->typeEntry()->qualifiedCppName()); + for (const auto &instantiation : metaClass->templateBaseClassInstantiations()) + result += processInstantiationsVariableName(instantiation); + appendIndexSuffix(&result); + return result; +} + +QString ShibokenGenerator::getTypeIndexVariableName(const AbstractMetaClassCPtr &metaClass) +{ + return getTypeIndexVariableName(metaClass->typeEntry()); +} +QString ShibokenGenerator::getTypeIndexVariableName(TypeEntryCPtr type) +{ + if (isCppPrimitive(type)) + type = basicReferencedTypeEntry(type); + QString result = u"SBK_"_s; + // Disambiguate namespaces per module to allow for extending them. + if (type->isNamespace()) { + QString package = type->targetLangPackage(); + const int dot = package.lastIndexOf(u'.'); + result += QStringView{package}.right(package.size() - (dot + 1)); + } + result += _fixedCppTypeName(type->qualifiedCppName()); + appendIndexSuffix(&result); + return result; +} +QString ShibokenGenerator::getTypeIndexVariableName(const AbstractMetaType &type) +{ + QString result = u"SBK"_s; + if (type.typeEntry()->isContainer()) + result += u'_' + moduleName(); + result += processInstantiationsVariableName(type); + appendIndexSuffix(&result); + return result; +} + +void collectfromTypeEntry(TypeEntryCPtr entry, QStringList &typeNames) +{ + if (entry->shouldGenerate()) { + typeNames[entry->sbkIndex()] = entry->qualifiedTargetLangName(); + if (entry->isEnum()) { + auto ete = std::static_pointer_cast<const EnumTypeEntry>(entry); + if (ete->flags()) { + auto entry = ete->flags(); + typeNames[entry->sbkIndex()] = entry->qualifiedTargetLangName(); + } + } + } +} + +void ShibokenGenerator::collectFullTypeNamesArray(QStringList &typeNames) +{ + for (const auto &metaClass : api().classes()) { + collectfromTypeEntry(metaClass->typeEntry(), typeNames); + + for (const AbstractMetaEnum &metaEnum : metaClass->enums()) + collectfromTypeEntry(metaEnum.typeEntry(), typeNames); + + int smartPointerCountIndex = getMaxTypeIndex(); + for (const auto &smp : api().instantiatedSmartPointers()) { + auto entry = smp.type.typeEntry(); + typeNames[smartPointerCountIndex] = entry->qualifiedTargetLangName(); + ++smartPointerCountIndex; + } + } + for (const AbstractMetaEnum &metaEnum : api().globalEnums()) + collectfromTypeEntry(metaEnum.typeEntry(), typeNames); +} + +bool ShibokenGenerator::verboseErrorMessagesDisabled() +{ + return m_options.verboseErrorMessagesDisabled; +} + +bool ShibokenGenerator::pythonFunctionWrapperUsesListOfArguments(const AbstractMetaFunctionCPtr &func) const +{ + const auto &groups = func->implementingClass() + ? getFunctionGroups(func->implementingClass()) + : getGlobalFunctionGroups(); + OverloadData od(groups.value(func->name()), api()); + return od.pythonFunctionWrapperUsesListOfArguments(); +} + +QString ShibokenGenerator::minimalConstructorExpression(const ApiExtractorResult &api, + const AbstractMetaType &type) +{ + if (type.isExtendedCppPrimitive() || type.isSmartPointer()) + return {}; + QString errorMessage; + const auto ctor = minimalConstructor(api, type, &errorMessage); + if (ctor.has_value()) + return ctor->initialization(); + + const QString message = + msgCouldNotFindMinimalConstructor(QLatin1StringView(__FUNCTION__), + type.cppSignature(), errorMessage); + qCWarning(lcShiboken()).noquote() << message; + return u";\n#error "_s + message + u'\n'; +} + +QString ShibokenGenerator::minimalConstructorExpression(const ApiExtractorResult &api, + const TypeEntryCPtr &type) +{ + if (isExtendedCppPrimitive(type)) + return {}; + const auto ctor = minimalConstructor(api, type); + if (ctor.has_value()) + return ctor->initialization(); + + const QString message = + msgCouldNotFindMinimalConstructor(QLatin1StringView(__FUNCTION__), + type->qualifiedCppName()); + qCWarning(lcShiboken()).noquote() << message; + return u";\n#error "_s + message + u'\n'; +} + +QString ShibokenGenerator::pythonArgsAt(int i) +{ + return PYTHON_ARGS + u'[' + QString::number(i) + u']'; +} + +void ShibokenGenerator::replaceTemplateVariables(QString &code, + const AbstractMetaFunctionCPtr &func) const +{ + const auto cpp_class = func->ownerClass(); + if (cpp_class) + code.replace(u"%TYPE"_s, cpp_class->name()); + + const AbstractMetaArgumentList &argument = func->arguments(); + for (const AbstractMetaArgument &arg : argument) + code.replace(u'%' + QString::number(arg.argumentIndex() + 1), arg.name()); + + //template values + code.replace(u"%RETURN_TYPE"_s, translateType(func->type(), cpp_class)); + code.replace(u"%FUNCTION_NAME"_s, func->originalName()); + + if (code.contains(u"%ARGUMENT_NAMES")) { + StringStream aux_stream; + writeArgumentNames(aux_stream, func, Generator::SkipRemovedArguments); + code.replace(u"%ARGUMENT_NAMES"_s, aux_stream); + } + + if (code.contains(u"%ARGUMENTS")) { + StringStream aux_stream; + writeFunctionArguments(aux_stream, func, Options(SkipDefaultValues) | SkipRemovedArguments); + code.replace(u"%ARGUMENTS"_s, aux_stream); + } +} + +QString ShibokenGenerator::stdMove(const QString &c) +{ + return u"std::move("_s + c + u')'; +} diff --git a/sources/shiboken6/generator/shiboken/shibokengenerator.h b/sources/shiboken6/generator/shiboken/shibokengenerator.h new file mode 100644 index 000000000..22ee73fa2 --- /dev/null +++ b/sources/shiboken6/generator/shiboken/shibokengenerator.h @@ -0,0 +1,491 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef SHIBOKENGENERATOR_H +#define SHIBOKENGENERATOR_H + +#include <generator.h> + +#include "customconversion_typedefs.h" +#include "abstractmetalang_enums.h" +#include "typesystem_typedefs.h" +#include "typesystem_enums.h" + +#include <QtCore/QRegularExpression> + +#include <array> +#include <optional> + +class EnumTypeEntry; +class FlagsTypeEntry; +class DocParser; +class CodeSnip; +class QPropertySpec; +class OverloadData; +class TargetToNativeConversion; +struct GeneratorClassInfoCacheEntry; +struct IncludeGroup; +struct ShibokenGeneratorOptions; + +class TextStream; + +// Function to be used for implementing nb_bool +struct BoolCastFunction +{ + AbstractMetaFunctionCPtr function; + bool invert = false; // Function is "isNull()", (invert result). +}; + +using BoolCastFunctionOptional = std::optional<BoolCastFunction>; + +/** + * Abstract generator that contains common methods used in CppGenerator and HeaderGenerator. + */ +class ShibokenGenerator : public Generator +{ +public: + Q_DISABLE_COPY_MOVE(ShibokenGenerator) + + /// Besides the actual bindings (see AbstractMetaFunction::generateBinding(), + /// some functions need to be generated into the wrapper class + /// (virtual method/avoid protected hack expose). + enum class FunctionGenerationFlag + { + None = 0x0, + /// Virtual method overridable in Python + VirtualMethod = 0x1, + /// Special QObject virtuals + QMetaObjectMethod = 0x2, + /// Needs a protected wrapper for avoidProtectedHack() + /// public "foo_protected()" calling "foo()" + ProtectedWrapper = 0x4, // + /// Pass through constructor + WrapperConstructor = 0x8, + /// Generate a special copy constructor + /// "FooBar_Wrapper(const Foo&)" for constructing a wrapper from a value + WrapperSpecialCopyConstructor = 0x10 + }; + Q_DECLARE_FLAGS(FunctionGeneration, FunctionGenerationFlag); + + enum class AttroCheckFlag + { + None = 0x0, + GetattroOverloads = 0x01, + GetattroSmartPointer = 0x02, + GetattroUser = 0x04, // Injected code + GetattroMask = 0x0F, + SetattroQObject = 0x10, + SetattroSmartPointer = 0x20, + SetattroMethodOverride = 0x40, + SetattroUser = 0x80, // Injected code + SetattroMask = 0xF0, + }; + Q_DECLARE_FLAGS(AttroCheck, AttroCheckFlag); + + using FunctionGroups = QMap<QString, AbstractMetaFunctionCList>; // Sorted + + ShibokenGenerator(); + ~ShibokenGenerator() override; + + const char *name() const override { return "Shiboken"; } + + static QList<OptionDescription> options(); + static std::shared_ptr<OptionsParser> createOptionsParser(); + + static QString minimalConstructorExpression(const ApiExtractorResult &api, + const AbstractMetaType &type); + static QString minimalConstructorExpression(const ApiExtractorResult &api, + const TypeEntryCPtr &type); + +protected: + bool doSetup() override; + + GeneratorContext contextForClass(const AbstractMetaClassCPtr &c) const override; + + /** + * Returns a map with all functions grouped, the function name is used as key. + * Example of return value: { "foo" -> ["foo(int)", "foo(int, long)], "bar" -> "bar(double)"} + * \param scope Where to search for functions, null means all global functions. + */ + FunctionGroups getGlobalFunctionGroups() const; + static FunctionGroups getFunctionGroups(const AbstractMetaClassCPtr &scope); + + static QList<AbstractMetaFunctionCList> + numberProtocolOperators(const AbstractMetaClassCPtr &scope); + + static BoolCastFunctionOptional boolCast(const AbstractMetaClassCPtr &scope); + + /** + * Returns all different inherited overloads of func, and includes func as well. + * The function can be called multiple times without duplication. + * \param func the metafunction to be searched in subclasses. + * \param seen the function's minimal signatures already seen. + */ + static AbstractMetaFunctionCList getFunctionAndInheritedOverloads(const AbstractMetaFunctionCPtr &func, + QSet<QString> *seen); + + /// Write user's custom code snippets at class or module level. + void writeClassCodeSnips(TextStream &s, + const QList<CodeSnip> &codeSnips, + TypeSystem::CodeSnipPosition position, + TypeSystem::Language language, + const GeneratorContext &context) const; + void writeCodeSnips(TextStream &s, + const QList<CodeSnip> &codeSnips, + TypeSystem::CodeSnipPosition position, + TypeSystem::Language language) const; + /// Write user's custom code snippets at function level. + void writeCodeSnips(TextStream &s, + const QList<CodeSnip> &codeSnips, + TypeSystem::CodeSnipPosition position, + TypeSystem::Language language, + const AbstractMetaFunctionCPtr &func, + bool usePyArgs, + const AbstractMetaArgument *lastArg) const; + + /// Replaces variables for the user's custom code at global or class level. + void processCodeSnip(QString &code) const; + void processCodeSnip(QString &code, const QString &context) const; + void processClassCodeSnip(QString &code, const GeneratorContext &context) const; + + /** + * Verifies if any of the function's code injections makes a call + * to the C++ method. This is used by the generator to avoid writing calls + * to C++ when the user custom code already does this. + * \param func the function to check + * \return true if the function's code snippets call the wrapped C++ function + */ + static bool injectedCodeCallsCppFunction(const GeneratorContext &context, + const AbstractMetaFunctionCPtr &func); + + /** + * Function which parse the metafunction information + * \param func the function witch will be parserd + * \param option some extra options + * \param arg_count the number of function arguments + */ + QString functionSignature(const AbstractMetaFunctionCPtr &func, + const QString &prepend = QString(), + const QString &append = QString(), + Options options = NoOption, + int arg_count = -1) const; + + /// Returns the top-most class that has multiple inheritance in the ancestry. + static AbstractMetaClassCPtr + getMultipleInheritingClass(const AbstractMetaClassCPtr &metaClass); + + static bool useOverrideCaching(const AbstractMetaClassCPtr &metaClass); + static AttroCheck checkAttroFunctionNeeds(const AbstractMetaClassCPtr &metaClass); + + /// Returns a list of methods of the given class where each one is part of + /// a different overload with both static and non-static method. + static AbstractMetaFunctionCList + getMethodsWithBothStaticAndNonStaticMethods(const AbstractMetaClassCPtr &metaClass); + + static void writeToPythonConversion(TextStream &s, + const AbstractMetaType &type, + const AbstractMetaClassCPtr &context, + const QString &argumentName); + static void writeToCppConversion(TextStream &s, + const AbstractMetaType &type, + const QString &inArgName, + const QString &outArgName); + static void writeToCppConversion(TextStream &s, + const AbstractMetaClassCPtr &metaClass, + const QString &inArgName, + const QString &outArgName); + + /// Returns true if the argument is a pointer that rejects nullptr values. + static bool shouldRejectNullPointerArgument(const AbstractMetaFunctionCPtr &func, + int argIndex); + + /// Verifies if the class should have a C++ wrapper generated for it, + /// instead of only a Python wrapper. + static bool shouldGenerateCppWrapper(const AbstractMetaClassCPtr &metaClass); + + static bool shouldGenerateMetaObjectFunctions(const AbstractMetaClassCPtr &metaClass); + + /// Returns which functions need to be generated into the wrapper class + static FunctionGeneration functionGeneration(const AbstractMetaFunctionCPtr &func); + + // Return a list of implicit conversions if generation is enabled. + AbstractMetaFunctionCList implicitConversions(const TypeEntryCPtr &t) const; + + static QString wrapperName(const AbstractMetaClassCPtr &metaClass); + + static QString fullPythonClassName(const AbstractMetaClassCPtr &metaClass); + + static QString headerFileNameForContext(const GeneratorContext &context); + IncludeGroup baseWrapperIncludes(const GeneratorContext &classContext) const; + + static QString fullPythonFunctionName(const AbstractMetaFunctionCPtr &func, bool forceFunc); + + static bool wrapperDiagnostics(); + + static QString protectedEnumSurrogateName(const AbstractMetaEnum &metaEnum); + + static QString pythonPrimitiveTypeName(const QString &cppTypeName); + + static QString pythonOperatorFunctionName(const AbstractMetaFunctionCPtr &func); + static QList<AbstractMetaFunctionCList> + filterGroupedOperatorFunctions(const AbstractMetaClassCPtr &metaClass, + OperatorQueryOptions query); + + static QString fixedCppTypeName(const TargetToNativeConversion &toNative); + static QString fixedCppTypeName(const AbstractMetaType &type); + static QString fixedCppTypeName(const TypeEntryCPtr &type, QString typeName = {}); + + static bool isNumber(const QString &cpythonApiName); + static bool isNumber(const TypeEntryCPtr &type); + static bool isNumber(const AbstractMetaType &type); + static bool isPyInt(const TypeEntryCPtr &type); + static bool isPyInt(const AbstractMetaType &type); + + static bool isNullPtr(const QString &value); + + static QString converterObject(const AbstractMetaType &type) ; + static QString converterObject(const TypeEntryCPtr &type); + + static QString cpythonBaseName(const AbstractMetaClassCPtr &metaClass); + static QString cpythonBaseName(const TypeEntryCPtr &type); + static QString containerCpythonBaseName(const ContainerTypeEntryCPtr &ctype); + static QString cpythonBaseName(const AbstractMetaType &type); + static QString cpythonTypeName(const AbstractMetaClassCPtr &metaClass); + static QString cpythonTypeName(const TypeEntryCPtr &type); + static QString cpythonTypeNameExtSet(const TypeEntryCPtr &type); + static QString cpythonTypeNameExtSet(const AbstractMetaType &type); + static QString cpythonTypeNameExt(const TypeEntryCPtr &type); + static QString cpythonTypeNameExt(const AbstractMetaType &type); + static QString cpythonCheckFunction(TypeEntryCPtr type); + static QString cpythonCheckFunction(AbstractMetaType metaType); + static QString cpythonIsConvertibleFunction(const TypeEntryCPtr &type); + static QString cpythonIsConvertibleFunction(const AbstractMetaType &metaType); + static QString cpythonIsConvertibleFunction(const AbstractMetaArgument &metaArg); + + static QString cpythonToCppConversionFunction(const AbstractMetaClassCPtr &metaClass) ; + static QString cpythonToCppConversionFunction(const AbstractMetaType &type); + static QString cpythonToPythonConversionFunction(const AbstractMetaType &type); + static QString cpythonToPythonConversionFunction(const AbstractMetaClassCPtr &metaClass); + static QString cpythonToPythonConversionFunction(const TypeEntryCPtr &type); + + static QString cpythonFunctionName(const AbstractMetaFunctionCPtr &func) ; + static QString cpythonMethodDefinitionName(const AbstractMetaFunctionCPtr &func); + static QString cpythonGettersSettersDefinitionName(const AbstractMetaClassCPtr &metaClass); + static QString cpythonGetattroFunctionName(const AbstractMetaClassCPtr &metaClass); + static QString cpythonSetattroFunctionName(const AbstractMetaClassCPtr &metaClass); + static QString cpythonGetterFunctionName(const AbstractMetaField &metaField); + static QString cpythonSetterFunctionName(const AbstractMetaField &metaField); + static QString cpythonGetterFunctionName(const QPropertySpec &property, + const AbstractMetaClassCPtr &metaClass); + static QString cpythonSetterFunctionName(const QPropertySpec &property, + const AbstractMetaClassCPtr &metaClass); + static QString cpythonWrapperCPtr(const AbstractMetaClassCPtr &metaClass, + const QString &argName = QLatin1StringView("self")); + static QString cpythonWrapperCPtr(const AbstractMetaType &metaType, + const QString &argName); + static QString cpythonWrapperCPtr(const TypeEntryCPtr &type, const QString &argName); + + static QString cpythonEnumName(const EnumTypeEntryCPtr &enumEntry); + static QString cpythonEnumName(const AbstractMetaEnum &metaEnum); + + static QString cpythonFlagsName(const FlagsTypeEntryCPtr &flagsEntry); + static QString cpythonFlagsName(const AbstractMetaEnum *metaEnum); + /// Returns the special cast function name, the function used to proper cast + /// class with multiple inheritance. + static QString cpythonSpecialCastFunctionName(const AbstractMetaClassCPtr &metaClass); + + /// Returns the file name for the module global header. If no module name + /// is provided the current will be used. + static QString getModuleHeaderFileName(const QString &moduleName = QString()); + static QString getPrivateModuleHeaderFileName(const QString &moduleName = QString()); + + /// Includes for header (native wrapper class) or binding source + QList<IncludeGroup> classIncludes(const AbstractMetaClassCPtr &metaClass) const; + + /// Returns true if the user enabled the so called "parent constructor heuristic". + static bool useCtorHeuristic(); + /// Returns true if the user enabled the so called "return value heuristic". + static bool useReturnValueHeuristic(); + /// Returns true if the generator should use the result of isNull()const to compute boolean casts. + static bool useIsNullAsNbBool(); + /// Whether to generate lean module headers + static bool leanHeaders(); + /// Returns true if the generator should use operator bool to compute boolean casts. + static bool useOperatorBoolAsNbBool(); + /// Generate implicit conversions of function arguments + static bool generateImplicitConversions(); + static QString cppApiVariableNameOld(const QString &moduleName = {}); + static QString cppApiVariableName(const QString &moduleName = QString()); + static QString pythonModuleObjectName(const QString &moduleName = QString()); + static QString convertersVariableName(const QString &moduleName = QString()); + /// Returns the type index variable name for a given class. + static QString getTypeIndexVariableName(const AbstractMetaClassCPtr &metaClass); + /// Returns the type index variable name for a given typedef for a template + /// class instantiation made of the template class and the instantiation values + static QString getTypeAlternateTemplateIndexVariableName(const AbstractMetaClassCPtr &metaClass); + static QString getTypeIndexVariableName(TypeEntryCPtr type); + static QString getTypeIndexVariableName(const AbstractMetaType &type) ; + + /// Collect all type names as an array for initializing the type/name struct. + void collectFullTypeNamesArray(QStringList &typeNames); + + /// Returns true if the user don't want verbose error messages on the generated bindings. + static bool verboseErrorMessagesDisabled(); + + void collectContainerTypesFromConverterMacros(const QString &code, bool toPythonMacro); + + static void writeFunctionCall(TextStream &s, + const AbstractMetaFunctionCPtr &metaFunc, + Options options = NoOption); + + // All data about extended converters: the type entries of the target type, and a + // list of AbstractMetaClasses accepted as argument for the conversion. + using ExtendedConverterData = QHash<TypeEntryCPtr, AbstractMetaClassCList>; + /// Returns all extended conversions for the current module. + ExtendedConverterData getExtendedConverters() const; + + /// Returns a list of converters for the non wrapper types of the current module. + static QList<CustomConversionPtr> getPrimitiveCustomConversions(); + + /// Returns true if the Python wrapper for the received OverloadData must accept a list of arguments. + bool pythonFunctionWrapperUsesListOfArguments(const AbstractMetaFunctionCPtr &func) const; + + static const QRegularExpression &convertToCppRegEx() + { return typeSystemConvRegExps()[TypeSystemToCppFunction]; } + + static QString pythonArgsAt(int i); + + /// Return the format character for C++->Python->C++ conversion (Py_BuildValue) + static const QHash<QString, QChar> &formatUnits(); + + static QString stdMove(const QString &c); + +private: + static QString getModuleHeaderFileBaseName(const QString &moduleName = QString()); + static QString cpythonGetterFunctionName(const QString &name, + const AbstractMetaClassCPtr &enclosingClass); + static QString cpythonSetterFunctionName(const QString &name, + const AbstractMetaClassCPtr &enclosingClass); + + static const GeneratorClassInfoCacheEntry & + getGeneratorClassInfo(const AbstractMetaClassCPtr &scope); + static FunctionGroups getFunctionGroupsImpl(const AbstractMetaClassCPtr &scope); + static QList<AbstractMetaFunctionCList> + getNumberProtocolOperators(const AbstractMetaClassCPtr &metaClass); + static BoolCastFunctionOptional getBoolCast(const AbstractMetaClassCPtr &metaClass); + static bool classNeedsGetattroFunctionImpl(const AbstractMetaClassCPtr &metaClass); + + QString translateTypeForWrapperMethod(const AbstractMetaType &cType, + const AbstractMetaClassCPtr &context, + Options opt = NoOption) const; + + /** + * Returns all different inherited overloads of func. + * The function can be called multiple times without duplication. + * \param func the metafunction to be searched in subclasses. + * \param seen the function's minimal signatures already seen. + */ + static void getInheritedOverloads(const AbstractMetaClassCPtr &scope, + AbstractMetaFunctionCList *overloads); + + + /** + * Write a function argument in the C++ in the text stream \p s. + * This function just call \code s << argumentString(); \endcode + * \param s text stream used to write the output. + * \param func the current metafunction. + * \param argument metaargument information to be parsed. + * \param options some extra options. + */ + void writeArgument(TextStream &s, + const AbstractMetaFunctionCPtr &func, + const AbstractMetaArgument &argument, + Options options = NoOption) const; + /** + * Create a QString in the C++ format to an function argument. + * \param func the current metafunction. + * \param argument metaargument information to be parsed. + * \param options some extra options. + */ + QString argumentString(const AbstractMetaFunctionCPtr &func, + const AbstractMetaArgument &argument, + Options options = NoOption) const; + + QString functionReturnType(const AbstractMetaFunctionCPtr &func, Options options = NoOption) const; + + /// Utility function for writeCodeSnips. + using ArgumentVarReplacementPair = std::pair<AbstractMetaArgument, QString>; + using ArgumentVarReplacementList = QList<ArgumentVarReplacementPair>; + static ArgumentVarReplacementList + getArgumentReplacement(const AbstractMetaFunctionCPtr &func, + bool usePyArgs, TypeSystem::Language language, + const AbstractMetaArgument *lastArg); + + /// Returns a string with the user's custom code snippets that comply with \p position and \p language. + static QString getCodeSnippets(const QList<CodeSnip> &codeSnips, + TypeSystem::CodeSnipPosition position, + TypeSystem::Language language); + + enum TypeSystemConverterVariable { + TypeSystemCheckFunction = 0, + TypeSystemIsConvertibleFunction, + TypeSystemToCppFunction, + TypeSystemToPythonFunction, + TypeSystemConverterVariables + }; + void replaceConverterTypeSystemVariable(TypeSystemConverterVariable converterVariable, + QString &code) const; + + /// Replaces the %CONVERTTOPYTHON type system variable. + inline void replaceConvertToPythonTypeSystemVariable(QString &code) const + { + replaceConverterTypeSystemVariable(TypeSystemToPythonFunction, code); + } + /// Replaces the %CONVERTTOCPP type system variable. + inline void replaceConvertToCppTypeSystemVariable(QString &code) const + { + replaceConverterTypeSystemVariable(TypeSystemToCppFunction, code); + } + /// Replaces the %ISCONVERTIBLE type system variable. + inline void replaceIsConvertibleToCppTypeSystemVariable(QString &code) const + { + replaceConverterTypeSystemVariable(TypeSystemIsConvertibleFunction, code); + } + /// Replaces the %CHECKTYPE type system variable. + inline void replaceTypeCheckTypeSystemVariable(QString &code) const + { + replaceConverterTypeSystemVariable(TypeSystemCheckFunction, code); + } + + /// Return a prefix with '_' suitable for names in C++ + static QString moduleCppPrefix(const QString &moduleName = QString()); + + /// Functions used to write the function arguments on the class buffer. + /// \param s the class output buffer + /// \param func the pointer to metafunction information + /// \param count the number of function arguments + /// \param options some extra options used during the parser + static void writeArgumentNames(TextStream &s, + const AbstractMetaFunctionCPtr &func, + Options option); + + void writeFunctionArguments(TextStream &s, + const AbstractMetaFunctionCPtr &func, + Options options = NoOption) const; + + void replaceTemplateVariables(QString &code, + const AbstractMetaFunctionCPtr &func) const; + + static ShibokenGeneratorOptions m_options; + + /// Type system converter variable replacement names and regular expressions. + static const QHash<int, QString> &typeSystemConvName(); + + using TypeSystemConverterRegExps = std::array<QRegularExpression, TypeSystemConverterVariables>; + static const TypeSystemConverterRegExps &typeSystemConvRegExps(); +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(ShibokenGenerator::FunctionGeneration); +Q_DECLARE_OPERATORS_FOR_FLAGS(ShibokenGenerator::AttroCheck); + +#endif // SHIBOKENGENERATOR_H |