diff options
Diffstat (limited to 'sources/shiboken6/ApiExtractor/abstractmetabuilder_helpers.cpp')
-rw-r--r-- | sources/shiboken6/ApiExtractor/abstractmetabuilder_helpers.cpp | 202 |
1 files changed, 202 insertions, 0 deletions
diff --git a/sources/shiboken6/ApiExtractor/abstractmetabuilder_helpers.cpp b/sources/shiboken6/ApiExtractor/abstractmetabuilder_helpers.cpp new file mode 100644 index 000000000..68eef737a --- /dev/null +++ b/sources/shiboken6/ApiExtractor/abstractmetabuilder_helpers.cpp @@ -0,0 +1,202 @@ +// 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 "abstractmetabuilder.h" +#include "abstractmetabuilder_p.h" +#include "abstractmetaenum.h" +#include "abstractmetafield.h" +#include "abstractmetalang.h" +#include "enumtypeentry.h" +#include "flagstypeentry.h" + +using namespace Qt::StringLiterals; + +using QStringViewList = QList<QStringView>; + +// Return a prefix to fully qualify value, eg: +// resolveScopePrefix("Class::NestedClass::Enum::Value1", "Enum::Value1") +// -> "Class::NestedClass::") +static QString resolveScopePrefixHelper(const QStringViewList &scopeList, + QStringView value) +{ + QString name; + for (qsizetype i = scopeList.size() - 1 ; i >= 0; --i) { + const QString prefix = scopeList.at(i).toString() + u"::"_s; + if (value.startsWith(prefix)) + name.clear(); + else + name.prepend(prefix); + } + return name; +} + +QString AbstractMetaBuilder::resolveScopePrefix(const AbstractMetaClassCPtr &scope, + QStringView value) +{ + if (!scope) + return {}; + const QString &qualifiedCppName = scope->qualifiedCppName(); + const QStringViewList scopeList = + QStringView{qualifiedCppName}.split(u"::"_s, Qt::SkipEmptyParts); + return resolveScopePrefixHelper(scopeList, value); +} + +// Return the scope for fully qualifying the enumeration value +static QString resolveEnumValueScopePrefix(const AbstractMetaEnum &metaEnum, + QStringView value) +{ + AbstractMetaClassCPtr scope = metaEnum.enclosingClass(); + if (!scope) + return {}; // global enum, value should work as is + const QString &qualifiedCppName = scope->qualifiedCppName(); + const QString &enumName = metaEnum.name(); + QStringViewList parts = + QStringView{qualifiedCppName}.split(u"::"_s, Qt::SkipEmptyParts); + // Append the type (as required for enum classes) unless it is an anonymous enum. + if (!metaEnum.isAnonymous()) + parts.append(QStringView{enumName}); + return resolveScopePrefixHelper(parts, value); +} + +bool AbstractMetaBuilderPrivate::isQualifiedCppIdentifier(QStringView e) +{ + return !e.isEmpty() && e.at(0).isLetter() + && std::all_of(e.cbegin() + 1, e.cend(), + [](QChar c) { return c.isLetterOrNumber() || c == u'_' || c == u':'; }); +} + +static bool isIntegerConstant(const QStringView expr) +{ + bool isNumber; + auto n = expr.toInt(&isNumber, /* guess base: 0x or decimal */ 0); + Q_UNUSED(n); + return isNumber; +} + +static bool isFloatConstant(const QStringView expr) +{ + bool isNumber; + auto d = expr.toDouble(&isNumber); + Q_UNUSED(d); + return isNumber; +} + +// Fix an enum default value: Add the enum/flag scope or fully qualified name +// to the default value, making it usable from Python wrapper code outside the +// owner class hierarchy. See TestEnum::testEnumDefaultValues(). +QString AbstractMetaBuilderPrivate::fixEnumDefault(const AbstractMetaType &type, + const QString &expr, + const AbstractMetaClassCPtr &klass) const +{ + // QFlags construct from integers, do not fix that + if (isIntegerConstant(expr)) + return expr; + + const QString field = qualifyStaticField(klass, expr); + if (!field.isEmpty()) + return field; + + const auto typeEntry = type.typeEntry(); + EnumTypeEntryCPtr enumTypeEntry; + FlagsTypeEntryCPtr flagsTypeEntry; + if (typeEntry->isFlags()) { + flagsTypeEntry = std::static_pointer_cast<const FlagsTypeEntry>(typeEntry); + enumTypeEntry = flagsTypeEntry->originator(); + } else { + Q_ASSERT(typeEntry->isEnum()); + enumTypeEntry = std::static_pointer_cast<const EnumTypeEntry>(typeEntry); + } + // Use the enum's qualified name (would otherwise be "QFlags<Enum>") + if (!enumTypeEntry->qualifiedCppName().contains(u"::")) + return expr; // Global enum, nothing to fix here + + // This is a somehow scoped enum + AbstractMetaEnum metaEnum = m_enums.value(enumTypeEntry); + + if (isQualifiedCppIdentifier(expr)) // A single enum value + return resolveEnumValueScopePrefix(metaEnum, expr) + expr; + + QString result; + // Is this a cast from integer or other type ("Enum(-1)" or "Options(0x10|0x20)"? + // Prepend the scope (assuming enum and flags are in the same scope). + auto parenPos = expr.indexOf(u'('); + const bool typeCast = parenPos != -1 && expr.endsWith(u')') + && isQualifiedCppIdentifier(QStringView{expr}.left(parenPos)); + if (typeCast) { + const QString prefix = + AbstractMetaBuilder::resolveScopePrefix(metaEnum.enclosingClass(), expr); + result += prefix; + parenPos += prefix.size(); + } + result += expr; + + // Extract "Option1 | Option2" from "Options(Option1 | Option2)" + QStringView innerExpression = typeCast + ? QStringView{result}.mid(parenPos + 1, result.size() - parenPos - 2) + : QStringView{result}; + + // Quick check for number "Options(0x4)" + if (isIntegerConstant(innerExpression)) + return result; + + // Quick check for single enum value "Options(Option1)" + if (isQualifiedCppIdentifier(innerExpression)) { + const QString prefix = resolveEnumValueScopePrefix(metaEnum, innerExpression); + result.insert(parenPos + 1, prefix); + return result; + } + + // Tokenize simple "A | B" expressions and qualify the enum values therein. + // Anything more complicated is left as is ATM. + if (!innerExpression.contains(u'|') || innerExpression.contains(u'&') + || innerExpression.contains(u'^') || innerExpression.contains(u'(') + || innerExpression.contains(u'~')) { + return result; + } + + const QList<QStringView> tokens = innerExpression.split(u'|', Qt::SkipEmptyParts); + QStringList qualifiedTokens; + qualifiedTokens.reserve(tokens.size()); + for (const auto &tokenIn : tokens) { + const auto token = tokenIn.trimmed(); + QString qualified = token.toString(); + if (!isIntegerConstant(token) && isQualifiedCppIdentifier(token)) + qualified.prepend(resolveEnumValueScopePrefix(metaEnum, token)); + qualifiedTokens.append(qualified); + } + const QString qualifiedExpression = qualifiedTokens.join(u" | "_s); + if (!typeCast) + return qualifiedExpression; + + result.replace(parenPos + 1, innerExpression.size(), qualifiedExpression); + return result; +} + +bool AbstractMetaBuilder::dontFixDefaultValue(QStringView expr) +{ + return expr.isEmpty() || expr == u"{}" || expr == u"nullptr" + || expr == u"NULL" || expr == u"true" || expr == u"false" + || (expr.startsWith(u'{') && expr.startsWith(u'}')) // initializer list + || (expr.startsWith(u'[') && expr.startsWith(u']')) // array + || expr.startsWith(u"Qt::") // Qt namespace constant + || isIntegerConstant(expr) || isFloatConstant(expr); +} + +QString AbstractMetaBuilderPrivate::qualifyStaticField(const AbstractMetaClassCPtr &c, + QStringView field) +{ + if (!c || c->fields().isEmpty()) + return {}; + // If there is a scope, ensure it matches the class + const auto lastQualifier = field.lastIndexOf(u"::"); + if (lastQualifier != -1 + && !c->qualifiedCppName().endsWith(field.left(lastQualifier))) { + return {}; + } + const auto fieldName = lastQualifier != -1 + ? field.mid(lastQualifier + 2) : field; + const auto fieldOpt = c->findField(fieldName); + if (!fieldOpt.has_value() || !fieldOpt.value().isStatic()) + return {}; + return AbstractMetaBuilder::resolveScopePrefix(c, field) + field.toString(); +} |