diff options
Diffstat (limited to 'sources/shiboken6/generator/shiboken/overloaddata.cpp')
-rw-r--r-- | sources/shiboken6/generator/shiboken/overloaddata.cpp | 1011 |
1 files changed, 1011 insertions, 0 deletions
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 |