aboutsummaryrefslogtreecommitdiffstats
path: root/sources/shiboken6/ApiExtractor/clangparser
diff options
context:
space:
mode:
Diffstat (limited to 'sources/shiboken6/ApiExtractor/clangparser')
-rw-r--r--sources/shiboken6/ApiExtractor/clangparser/clangbuilder.cpp1296
-rw-r--r--sources/shiboken6/ApiExtractor/clangparser/clangbuilder.h37
-rw-r--r--sources/shiboken6/ApiExtractor/clangparser/clangdebugutils.cpp175
-rw-r--r--sources/shiboken6/ApiExtractor/clangparser/clangdebugutils.h26
-rw-r--r--sources/shiboken6/ApiExtractor/clangparser/clangparser.cpp323
-rw-r--r--sources/shiboken6/ApiExtractor/clangparser/clangparser.h88
-rw-r--r--sources/shiboken6/ApiExtractor/clangparser/clangutils.cpp320
-rw-r--r--sources/shiboken6/ApiExtractor/clangparser/clangutils.h108
-rw-r--r--sources/shiboken6/ApiExtractor/clangparser/compilersupport.cpp456
-rw-r--r--sources/shiboken6/ApiExtractor/clangparser/compilersupport.h56
10 files changed, 2885 insertions, 0 deletions
diff --git a/sources/shiboken6/ApiExtractor/clangparser/clangbuilder.cpp b/sources/shiboken6/ApiExtractor/clangparser/clangbuilder.cpp
new file mode 100644
index 000000000..31e7efb05
--- /dev/null
+++ b/sources/shiboken6/ApiExtractor/clangparser/clangbuilder.cpp
@@ -0,0 +1,1296 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "clangbuilder.h"
+#include "compilersupport.h"
+#include "clangutils.h"
+#include "clangdebugutils.h"
+
+#include <codemodel.h>
+#include <reporthandler.h>
+
+#include "qtcompat.h"
+
+#include <QtCore/QDebug>
+#include <QtCore/QDir>
+#include <QtCore/QHash>
+#include <QtCore/QMap>
+#include <QtCore/QString>
+#include <QtCore/QStack>
+#include <QtCore/QList>
+
+#include <cstring>
+#include <ctype.h>
+
+using namespace Qt::StringLiterals;
+
+namespace clang {
+
+static inline bool isClassCursor(const CXCursor &c)
+{
+ return c.kind == CXCursor_ClassDecl || c.kind == CXCursor_StructDecl
+ || c.kind == CXCursor_ClassTemplate
+ || c.kind == CXCursor_ClassTemplatePartialSpecialization;
+}
+
+static inline bool isClassOrNamespaceCursor(const CXCursor &c)
+{
+ return c.kind == CXCursor_Namespace || isClassCursor(c);
+}
+
+static inline bool withinClassDeclaration(const CXCursor &cursor)
+{
+ return isClassCursor(clang_getCursorLexicalParent(cursor));
+}
+
+static QString fixTypeName(QString t)
+{
+ // Fix "Foo &" -> "Foo&", similarly "Bar **" -> "Bar**"
+ auto pos = t.size() - 1;
+ for (; pos >= 0 && (t.at(pos) == u'&' || t.at(pos) == u'*'); --pos) {}
+ if (pos > 0 && t.at(pos) == u' ')
+ t.remove(pos, 1);
+ return t;
+}
+
+// Insert template parameter to class name: "Foo<>" -> "Foo<T1>" -> "Foo<T1,T2>"
+// This needs to be done immediately when template parameters are encountered since
+// the class name "Foo<T1,T2>" is the scope for nested items.
+static bool insertTemplateParameterIntoClassName(const QString &parmName, QString *name)
+{
+ if (Q_UNLIKELY(!name->endsWith(u'>')))
+ return false;
+ const bool needsComma = name->at(name->size() - 2) != u'<';
+ const auto insertionPos = name->size() - 1;
+ name->insert(insertionPos, parmName);
+ if (needsComma)
+ name->insert(insertionPos, u',');
+ return true;
+}
+
+static inline bool insertTemplateParameterIntoClassName(const QString &parmName,
+ const ClassModelItem &item)
+{
+ QString name = item->name();
+ const bool result = insertTemplateParameterIntoClassName(parmName, &name);
+ item->setName(name);
+ return result;
+}
+
+static inline Access accessPolicy(CX_CXXAccessSpecifier access)
+{
+ Access result = Access::Public;
+ switch (access) {
+ case CX_CXXProtected:
+ result = Access::Protected;
+ break;
+ case CX_CXXPrivate:
+ result = Access::Private;
+ break;
+ default:
+ break;
+ }
+ return result;
+}
+
+static bool isSigned(CXTypeKind kind)
+{
+ switch (kind) {
+ case CXType_UChar:
+ case CXType_Char16:
+ case CXType_Char32:
+ case CXType_UShort:
+ case CXType_UInt:
+ case CXType_ULong:
+ case CXType_ULongLong:
+ case CXType_UInt128:
+ return false;
+ default:
+ break;
+ }
+ return true;
+}
+
+class BuilderPrivate {
+public:
+ Q_DISABLE_COPY_MOVE(BuilderPrivate)
+
+ enum class SpecialSystemHeader {
+ None,
+ Types,
+ OpenGL,
+ WhiteListed,
+ WhiteListedPath
+ };
+
+ using CursorClassHash = QHash<CXCursor, ClassModelItem>;
+ using TypeInfoHash = QHash<CXType, TypeInfo>;
+
+ explicit BuilderPrivate(BaseVisitor *bv) : m_baseVisitor(bv), m_model(new CodeModel)
+ {
+ m_scopeStack.push(NamespaceModelItem(new _FileModelItem(m_model)));
+ }
+ ~BuilderPrivate()
+ {
+ delete m_model;
+ }
+
+ // Determine scope from top item. Note that the scope list does not necessarily
+ // match the scope stack in case of forward-declared inner classes whose definition
+ // appears in the translation unit while the scope is the outer class.
+ void updateScope()
+ {
+ if (m_scopeStack.size() <= 1)
+ m_scope.clear();
+ else
+ m_scope = m_scopeStack.back()->scope() << m_scopeStack.back()->name();
+ }
+
+ void pushScope(const ScopeModelItem &i)
+ {
+ m_scopeStack.push(i);
+ updateScope();
+ }
+
+ void popScope()
+ {
+ m_scopeStack.back()->purgeClassDeclarations();
+ m_scopeStack.pop();
+ updateScope();
+ }
+
+ bool addClass(const CXCursor &cursor, CodeModel::ClassType t);
+ FunctionModelItem createFunction(const CXCursor &cursor,
+ CodeModel::FunctionType t = CodeModel::Normal,
+ bool isTemplateCode = false);
+ FunctionModelItem createMemberFunction(const CXCursor &cursor,
+ bool isTemplateCode = false);
+ void qualifyConstructor(const CXCursor &cursor);
+ TypeInfo createTypeInfoUncached(const CXType &type,
+ bool *cacheable = nullptr) const;
+ TypeInfo createTypeInfo(const CXType &type) const;
+ TypeInfo createTypeInfo(const CXCursor &cursor) const
+ { return createTypeInfo(clang_getCursorType(cursor)); }
+ void addTemplateInstantiations(const CXType &type,
+ QString *typeName,
+ TypeInfo *t) const;
+ bool addTemplateInstantiationsRecursion(const CXType &type, TypeInfo *t) const;
+
+ void addTypeDef(const CXCursor &cursor, const CXType &cxType);
+ ClassModelItem currentTemplateClass() const;
+ void startTemplateTypeAlias(const CXCursor &cursor);
+ void endTemplateTypeAlias(const CXCursor &typeAliasCursor);
+
+ TemplateParameterModelItem createTemplateParameter(const CXCursor &cursor) const;
+ TemplateParameterModelItem createNonTypeTemplateParameter(const CXCursor &cursor) const;
+ void addField(const CXCursor &cursor);
+
+ static QString cursorValueExpression(BaseVisitor *bv, const CXCursor &cursor);
+ std::pair<QString, ClassModelItem> getBaseClass(CXType type) const;
+ void addBaseClass(const CXCursor &cursor);
+
+ SpecialSystemHeader specialSystemHeader(const QString &fileName) const;
+ bool visitHeader(const QString &fileName) const;
+ static const char *specialSystemHeaderReason(SpecialSystemHeader sh);
+
+ void setFileName(const CXCursor &cursor, _CodeModelItem *item);
+
+ BaseVisitor *m_baseVisitor;
+ CodeModel *m_model;
+
+ QStack<ScopeModelItem> m_scopeStack;
+ QStringList m_scope;
+ // Store all classes by cursor so that base classes can be found and inner
+ // classes can be correctly parented in case of forward-declared inner classes
+ // (QMetaObject::Connection)
+ CursorClassHash m_cursorClassHash;
+
+ mutable TypeInfoHash m_typeInfoHash; // Cache type information
+ mutable QHash<QString, TemplateTypeAliasModelItem> m_templateTypeAliases;
+
+ ClassModelItem m_currentClass;
+ EnumModelItem m_currentEnum;
+ FunctionModelItem m_currentFunction;
+ ArgumentModelItem m_currentArgument;
+ VariableModelItem m_currentField;
+ TemplateTypeAliasModelItem m_currentTemplateTypeAlias;
+ QStringList m_forceProcessSystemIncludes; // files, like "memory"
+ QStringList m_forceProcessSystemIncludePaths; // paths, like "/usr/include/Qt/"
+ QString m_usingTypeRef; // Base classes in "using Base::member;"
+ bool m_withinUsingDeclaration = false;
+
+ int m_anonymousEnumCount = 0;
+ CodeModel::FunctionType m_currentFunctionType = CodeModel::Normal;
+ bool m_withinFriendDecl = false;
+ mutable QHash<QString, SpecialSystemHeader> m_systemHeaders;
+};
+
+bool BuilderPrivate::addClass(const CXCursor &cursor, CodeModel::ClassType t)
+{
+ QString className = getCursorSpelling(cursor);
+ m_currentClass.reset(new _ClassModelItem(m_model, className));
+ setFileName(cursor, m_currentClass.get());
+ m_currentClass->setClassType(t);
+ // Some inner class? Note that it does not need to be (lexically) contained in a
+ // class since it is possible to forward declare an inner class:
+ // class QMetaObject { class Connection; }
+ // class QMetaObject::Connection {}
+ const CXCursor semPar = clang_getCursorSemanticParent(cursor);
+ if (isClassCursor(semPar)) {
+ const CursorClassHash::const_iterator it = m_cursorClassHash.constFind(semPar);
+ if (it == m_cursorClassHash.constEnd()) {
+ QString message;
+ QTextStream(&message) << "Unable to find containing class \""
+ << getCursorSpelling(semPar) << "\" of inner class \""
+ << className << "\".";
+ // PYSIDE-1501: Has been observed to fail for inner class of
+ // template with separated implementation where a forward
+ // declaration of the outer template is reported (Boost).
+ const auto severity = semPar.kind == CXCursor_ClassTemplate
+ ? CXDiagnostic_Warning : CXDiagnostic_Error;
+ const Diagnostic d(message, cursor, severity);
+ qWarning() << d;
+ m_baseVisitor->appendDiagnostic(d);
+ return false;
+ }
+ const ClassModelItem &containingClass = it.value();
+ containingClass->addClass(m_currentClass);
+ m_currentClass->setScope(containingClass->scope() << containingClass->name());
+ } else {
+ m_currentClass->setScope(m_scope);
+ m_scopeStack.back()->addClass(m_currentClass);
+ }
+ pushScope(m_currentClass);
+ m_cursorClassHash.insert(cursor, m_currentClass);
+ return true;
+}
+
+static QString msgCannotDetermineException(const std::string_view &snippetV)
+{
+ const auto newLine = snippetV.find('\n'); // Multiline noexcept specifications have been found in Qt
+ const bool truncate = newLine != std::string::npos;
+ const qsizetype length = qsizetype(truncate ? newLine : snippetV.size());
+ QString snippet = QString::fromUtf8(snippetV.data(), length);
+ if (truncate)
+ snippet += "..."_L1;
+
+ return u"Cannot determine exception specification: \""_s + snippet + u'"';
+}
+
+// Return whether noexcept(<value>) throws. noexcept() takes a constexpr value.
+// Try to determine the simple cases (true|false) via code snippet.
+static ExceptionSpecification computedExceptionSpecificationFromClang(BaseVisitor *bv,
+ const CXCursor &cursor,
+ bool isTemplateCode)
+{
+ const std::string_view snippet = bv->getCodeSnippet(cursor);
+ if (snippet.empty())
+ return ExceptionSpecification::Unknown; // Macro expansion, cannot tell
+ if (snippet.find("noexcept(false)") != std::string::npos)
+ return ExceptionSpecification::Throws;
+ if (snippet.find("noexcept(true)") != std::string::npos)
+ return ExceptionSpecification::NoExcept;
+ // Warn about it unless it is some form of template code where it is common
+ // to have complicated code, which is of no concern to shiboken, like:
+ // "QList::emplace(T) noexcept(is_pod<T>)".
+ if (!isTemplateCode && ReportHandler::isDebug(ReportHandler::FullDebug)) {
+ const Diagnostic d(msgCannotDetermineException(snippet), cursor, CXDiagnostic_Warning);
+ qWarning() << d;
+ bv->appendDiagnostic(d);
+ }
+ return ExceptionSpecification::Unknown;
+}
+
+static ExceptionSpecification exceptionSpecificationFromClang(BaseVisitor *bv,
+ const CXCursor &cursor,
+ bool isTemplateCode)
+{
+ const auto ce = clang_getCursorExceptionSpecificationType(cursor);
+ switch (ce) {
+ case CXCursor_ExceptionSpecificationKind_ComputedNoexcept:
+ return computedExceptionSpecificationFromClang(bv, cursor, isTemplateCode);
+ case CXCursor_ExceptionSpecificationKind_BasicNoexcept:
+ case CXCursor_ExceptionSpecificationKind_DynamicNone: // throw()
+ case CXCursor_ExceptionSpecificationKind_NoThrow:
+ return ExceptionSpecification::NoExcept;
+ case CXCursor_ExceptionSpecificationKind_Dynamic: // throw(t1..)
+ case CXCursor_ExceptionSpecificationKind_MSAny: // throw(...)
+ return ExceptionSpecification::Throws;
+ default:
+ // CXCursor_ExceptionSpecificationKind_None,
+ // CXCursor_ExceptionSpecificationKind_Unevaluated,
+ // CXCursor_ExceptionSpecificationKind_Uninstantiated
+ break;
+ }
+ return ExceptionSpecification::Unknown;
+}
+
+FunctionModelItem BuilderPrivate::createFunction(const CXCursor &cursor,
+ CodeModel::FunctionType t,
+ bool isTemplateCode)
+{
+ QString name = getCursorSpelling(cursor);
+ // Apply type fixes to "operator X &" -> "operator X&"
+ if (name.startsWith(u"operator "))
+ name = fixTypeName(name);
+ auto result = std::make_shared<_FunctionModelItem>(m_model, name);
+ setFileName(cursor, result.get());
+ const auto type = clang_getCursorResultType(cursor);
+ result->setType(createTypeInfo(type));
+ result->setScopeResolution(hasScopeResolution(type));
+ result->setFunctionType(t);
+ result->setScope(m_scope);
+ result->setStatic(clang_Cursor_getStorageClass(cursor) == CX_SC_Static);
+ result->setExceptionSpecification(exceptionSpecificationFromClang(m_baseVisitor, cursor, isTemplateCode));
+ switch (clang_getCursorAvailability(cursor)) {
+ case CXAvailability_Available:
+ break;
+ case CXAvailability_Deprecated:
+ result->setAttribute(FunctionAttribute::Deprecated);
+ break;
+ case CXAvailability_NotAvailable: // "Foo(const Foo&) = delete;"
+ result->setDeleted(true);
+ break;
+ case CXAvailability_NotAccessible:
+ break;
+ }
+ return result;
+}
+
+static inline CodeModel::FunctionType functionTypeFromCursor(const CXCursor &cursor)
+{
+ CodeModel::FunctionType result = CodeModel::Normal;
+ switch (cursor.kind) {
+ case CXCursor_Constructor:
+ if (clang_CXXConstructor_isCopyConstructor(cursor) != 0)
+ result = CodeModel::CopyConstructor;
+ else if (clang_CXXConstructor_isMoveConstructor(cursor) != 0)
+ result = CodeModel::MoveConstructor;
+ else
+ result = CodeModel::Constructor;
+ break;
+ case CXCursor_Destructor:
+ result = CodeModel::Destructor;
+ break;
+ default:
+ break;
+ }
+ return result;
+}
+
+FunctionModelItem BuilderPrivate::createMemberFunction(const CXCursor &cursor,
+ bool isTemplateCode)
+{
+ const CodeModel::FunctionType functionType =
+ m_currentFunctionType == CodeModel::Signal || m_currentFunctionType == CodeModel::Slot
+ ? m_currentFunctionType // by annotation
+ : functionTypeFromCursor(cursor);
+ isTemplateCode |= m_currentClass->name().endsWith(u'>');
+ auto result = createFunction(cursor, functionType, isTemplateCode);
+ result->setAccessPolicy(accessPolicy(clang_getCXXAccessSpecifier(cursor)));
+ result->setConstant(clang_CXXMethod_isConst(cursor) != 0);
+ result->setAttribute(FunctionAttribute::Static, clang_CXXMethod_isStatic(cursor) != 0);
+ result->setAttribute(FunctionAttribute::Virtual, clang_CXXMethod_isVirtual(cursor) != 0);
+ result->setAttribute(FunctionAttribute::Abstract, clang_CXXMethod_isPureVirtual(cursor) != 0);
+ return result;
+}
+
+// For CXCursor_Constructor, on endToken().
+void BuilderPrivate::qualifyConstructor(const CXCursor &cursor)
+{
+ // Clang does not tell us whether a constructor is explicit, preventing it
+ // from being used for implicit conversions. Try to guess whether a
+ // constructor is explicit in the C++99 sense (1 parameter) by checking for
+ // isConvertingConstructor() == 0. Fixme: The notion of "isConvertingConstructor"
+ // should be used in the code model instead of "explicit"
+ if (clang_CXXConstructor_isDefaultConstructor(cursor) == 0
+ && m_currentFunction->arguments().size() == 1
+ && clang_CXXConstructor_isCopyConstructor(cursor) == 0
+ && clang_CXXConstructor_isMoveConstructor(cursor) == 0) {
+ m_currentFunction->setAttribute(FunctionAttribute::Explicit,
+ clang_CXXConstructor_isConvertingConstructor(cursor) == 0);
+ }
+}
+
+TemplateParameterModelItem BuilderPrivate::createTemplateParameter(const CXCursor &cursor) const
+{
+ return std::make_shared<_TemplateParameterModelItem>(m_model, getCursorSpelling(cursor));
+}
+
+TemplateParameterModelItem BuilderPrivate::createNonTypeTemplateParameter(const CXCursor &cursor) const
+{
+ TemplateParameterModelItem result = createTemplateParameter(cursor);
+ result->setType(createTypeInfo(clang_getCursorType(cursor)));
+ return result;
+}
+
+// CXCursor_VarDecl, CXCursor_FieldDecl cursors
+void BuilderPrivate::addField(const CXCursor &cursor)
+{
+ auto field = std::make_shared<_VariableModelItem>(m_model, getCursorSpelling(cursor));
+ field->setAccessPolicy(accessPolicy(clang_getCXXAccessSpecifier(cursor)));
+ field->setScope(m_scope);
+ field->setType(createTypeInfo(cursor));
+ field->setMutable(clang_CXXField_isMutable(cursor) != 0);
+ setFileName(cursor, field.get());
+ m_currentField = field;
+ m_scopeStack.back()->addVariable(field);
+}
+
+// Create qualified name "std::list<std::string>" -> ("std", "list<std::string>")
+static QStringList qualifiedName(const QString &t)
+{
+ QStringList result;
+ int end = t.indexOf(u'<');
+ if (end == -1)
+ end = t.indexOf(u'(');
+ if (end == -1)
+ end = t.size();
+ int lastPos = 0;
+ while (true) {
+ const int nextPos = t.indexOf(u"::"_s, lastPos);
+ if (nextPos < 0 || nextPos >= end)
+ break;
+ result.append(t.mid(lastPos, nextPos - lastPos));
+ lastPos = nextPos + 2;
+ }
+ result.append(t.right(t.size() - lastPos));
+ return result;
+}
+
+static bool isArrayType(CXTypeKind k)
+{
+ return k == CXType_ConstantArray || k == CXType_IncompleteArray
+ || k == CXType_VariableArray || k == CXType_DependentSizedArray;
+}
+
+static bool isPointerType(CXTypeKind k)
+{
+ return k == CXType_Pointer || k == CXType_LValueReference || k == CXType_RValueReference;
+}
+
+bool BuilderPrivate::addTemplateInstantiationsRecursion(const CXType &type, TypeInfo *t) const
+{
+ // Template arguments
+ switch (type.kind) {
+ case CXType_Elaborated:
+ case CXType_Record:
+ case CXType_Unexposed:
+ if (const int numTemplateArguments = qMax(0, clang_Type_getNumTemplateArguments(type))) {
+ for (unsigned tpl = 0; tpl < unsigned(numTemplateArguments); ++tpl) {
+ const CXType argType = clang_Type_getTemplateArgumentAsType(type, tpl);
+ // CXType_Invalid is returned when hitting on a specialization
+ // of a non-type template (template <int v>).
+ if (argType.kind == CXType_Invalid)
+ return false;
+ t->addInstantiation(createTypeInfoUncached(argType));
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ return true;
+}
+
+static void dummyTemplateArgumentHandler(int, QStringView) {}
+
+void BuilderPrivate::addTemplateInstantiations(const CXType &type,
+ QString *typeName,
+ TypeInfo *t) const
+{
+ // In most cases, for templates like "Vector<A>", Clang will give us the
+ // arguments by recursing down the type. However this will fail for example
+ // within template classes (for functions like the copy constructor):
+ // template <class T>
+ // class Vector {
+ // Vector(const Vector&);
+ // };
+ // In that case, have TypeInfo parse the list from the spelling.
+ // Finally, remove the list "<>" from the type name.
+ const bool parsed = addTemplateInstantiationsRecursion(type, t)
+ && !t->instantiations().isEmpty();
+ if (!parsed)
+ t->setInstantiations({});
+ const auto pos = parsed
+ ? parseTemplateArgumentList(*typeName, dummyTemplateArgumentHandler)
+ : t->parseTemplateArgumentList(*typeName);
+ if (pos.first != -1 && pos.second != -1 && pos.second > pos.first)
+ typeName->remove(pos.first, pos.second - pos.first);
+}
+
+TypeInfo BuilderPrivate::createTypeInfoUncached(const CXType &type,
+ bool *cacheable) const
+{
+ if (type.kind == CXType_Pointer) { // Check for function pointers, first.
+ const CXType pointeeType = clang_getPointeeType(type);
+ const int argCount = clang_getNumArgTypes(pointeeType);
+ if (argCount >= 0) {
+ TypeInfo result = createTypeInfoUncached(clang_getResultType(pointeeType),
+ cacheable);
+ result.setFunctionPointer(true);
+ for (int a = 0; a < argCount; ++a)
+ result.addArgument(createTypeInfoUncached(clang_getArgType(pointeeType, unsigned(a)),
+ cacheable));
+ return result;
+ }
+ }
+
+ TypeInfo typeInfo;
+
+ CXType nestedType = type;
+ for (; isArrayType(nestedType.kind); nestedType = clang_getArrayElementType(nestedType)) {
+ const long long size = clang_getArraySize(nestedType);
+ typeInfo.addArrayElement(size >= 0 ? QString::number(size) : QString());
+ }
+
+ TypeInfo::Indirections indirections;
+ for (; isPointerType(nestedType.kind); nestedType = clang_getPointeeType(nestedType)) {
+ switch (nestedType.kind) {
+ case CXType_Pointer:
+ indirections.prepend(clang_isConstQualifiedType(nestedType) != 0
+ ? Indirection::ConstPointer : Indirection::Pointer);
+ break;
+ case CXType_LValueReference:
+ typeInfo.setReferenceType(LValueReference);
+ break;
+ case CXType_RValueReference:
+ typeInfo.setReferenceType(RValueReference);
+ break;
+ default:
+ break;
+ }
+ }
+ typeInfo.setIndirectionsV(indirections);
+
+ typeInfo.setConstant(clang_isConstQualifiedType(nestedType) != 0);
+ typeInfo.setVolatile(clang_isVolatileQualifiedType(nestedType) != 0);
+
+ QString typeName = getResolvedTypeName(nestedType);
+ while (TypeInfo::stripLeadingConst(&typeName)
+ || TypeInfo::stripLeadingVolatile(&typeName)) {
+ }
+
+ // For typedefs within templates or nested classes within templates (iterators):
+ // "template <class T> class QList { using Value=T; .."
+ // the typedef source is named "type-parameter-0-0". Convert it back to the
+ // template parameter name. The CXTypes are the same for all templates and
+ // must not be cached.
+ if (m_currentClass && typeName.startsWith(u"type-parameter-0-")) {
+ if (cacheable != nullptr)
+ *cacheable = false;
+ bool ok;
+ const int n = QStringView{typeName}.mid(17).toInt(&ok);
+ if (ok) {
+ auto currentTemplate = currentTemplateClass();
+ if (currentTemplate && n < currentTemplate->templateParameters().size())
+ typeName = currentTemplate->templateParameters().at(n)->name();
+ }
+ }
+
+ // Obtain template instantiations if the name has '<' (thus excluding
+ // typedefs like "std::string".
+ if (typeName.contains(u'<'))
+ addTemplateInstantiations(nestedType, &typeName, &typeInfo);
+
+ typeInfo.setQualifiedName(qualifiedName(typeName));
+ // 3320:CINDEX_LINKAGE int clang_getNumArgTypes(CXType T); function ptr types?
+ typeInfo.simplifyStdType();
+ return typeInfo;
+}
+
+TypeInfo BuilderPrivate::createTypeInfo(const CXType &type) const
+{
+ const auto it = m_typeInfoHash.constFind(type);
+ if (it != m_typeInfoHash.constEnd())
+ return it.value();
+ bool cacheable = true;
+ TypeInfo result = createTypeInfoUncached(type, &cacheable);
+ if (cacheable)
+ m_typeInfoHash.insert(type, result);
+ return result;
+}
+
+void BuilderPrivate::addTypeDef(const CXCursor &cursor, const CXType &cxType)
+{
+ const QString target = getCursorSpelling(cursor);
+ auto item = std::make_shared<_TypeDefModelItem>(m_model, target);
+ setFileName(cursor, item.get());
+ item->setType(createTypeInfo(cxType));
+ item->setScope(m_scope);
+ m_scopeStack.back()->addTypeDef(item);
+}
+
+ClassModelItem BuilderPrivate::currentTemplateClass() const
+{
+ for (auto i = m_scopeStack.size() - 1; i >= 0; --i) {
+ auto klass = std::dynamic_pointer_cast<_ClassModelItem>(m_scopeStack.at(i));
+ if (klass && klass->isTemplate())
+ return klass;
+ }
+ return {};
+}
+
+void BuilderPrivate::startTemplateTypeAlias(const CXCursor &cursor)
+{
+ const QString target = getCursorSpelling(cursor);
+ m_currentTemplateTypeAlias.reset(new _TemplateTypeAliasModelItem(m_model, target));
+ setFileName(cursor, m_currentTemplateTypeAlias.get());
+ m_currentTemplateTypeAlias->setScope(m_scope);
+}
+
+void BuilderPrivate::endTemplateTypeAlias(const CXCursor &typeAliasCursor)
+{
+ CXType type = clang_getTypedefDeclUnderlyingType(typeAliasCursor);
+ // Usually "<elaborated>std::list<T>" or "<unexposed>Container1<T>",
+ // as obtained with parser of PYSIDE-323
+ if (type.kind == CXType_Unexposed || type.kind == CXType_Elaborated) {
+ m_currentTemplateTypeAlias->setType(createTypeInfo(type));
+ m_scopeStack.back()->addTemplateTypeAlias(m_currentTemplateTypeAlias);
+ }
+ m_currentTemplateTypeAlias.reset();
+}
+
+// extract an expression from the cursor via source
+// CXCursor_EnumConstantDecl, ParmDecl (a = Flag1 | Flag2)
+QString BuilderPrivate::cursorValueExpression(BaseVisitor *bv, const CXCursor &cursor)
+{
+ const std::string_view snippet = bv->getCodeSnippet(cursor);
+ auto equalSign = snippet.find('=');
+ if (equalSign == std::string::npos)
+ return QString();
+ ++equalSign;
+ QString result = QString::fromLocal8Bit(snippet.data() + equalSign,
+ qsizetype(snippet.size() - equalSign));
+ // Fix a default expression as read from code. Simplify white space
+ result.remove(u'\r');
+ return result.contains(u'"') ? result.trimmed() : result.simplified();
+}
+
+// Resolve a type (loop over aliases/typedefs), for example for base classes
+// Note: TypeAliasTemplateDecl ("using QVector<T>=QList<T>") is automatically
+// resolved by clang_getTypeDeclaration(), but it stops at
+// TypeAliasDecl / TypedefDecl.
+
+struct TypeDeclaration
+{
+ CXType type;
+ CXCursor declaration;
+};
+
+static inline bool isTypeAliasDecl(const CXCursor &cursor)
+{
+ const auto kind = clang_getCursorKind(cursor);
+ return kind == CXCursor_TypeAliasDecl || kind == CXCursor_TypedefDecl;
+}
+
+static TypeDeclaration resolveBaseClassType(CXType type)
+{
+ CXCursor decl = clang_getTypeDeclaration(type);
+ auto resolvedType = clang_getCursorType(decl);
+ if (resolvedType.kind != CXType_Invalid && resolvedType.kind != type.kind)
+ type = resolvedType;
+ while (isTypeAliasDecl(decl)) {
+ type = clang_getTypedefDeclUnderlyingType(decl);
+ decl = clang_getTypeDeclaration(type);
+ }
+ return {type, decl};
+}
+
+// Note: Return the baseclass for cursors like CXCursor_CXXBaseSpecifier,
+// where the cursor spelling has "struct baseClass".
+std::pair<QString, ClassModelItem> BuilderPrivate::getBaseClass(CXType type) const
+{
+ const auto decl = resolveBaseClassType(type);
+ // Note: spelling has "struct baseClass", use type
+ QString baseClassName = getTypeName(decl.type);
+ if (baseClassName.startsWith(u"std::")) // Simplify "std::" types
+ baseClassName = createTypeInfo(decl.type).toString();
+
+ auto it = m_cursorClassHash.constFind(decl.declaration);
+ // Not found: Set unqualified name. This happens in cases like
+ // "class X : public std::list<...>", "template<class T> class Foo : public T"
+ // and standard types like true_type, false_type.
+ if (it == m_cursorClassHash.constEnd())
+ return {baseClassName, {}};
+
+ // Completely qualify the class name by looking it up and taking its scope
+ // plus the actual baseClass stripped off any scopes. Consider:
+ // namespace std {
+ // template <class T> class vector {};
+ // namespace n {
+ // class Foo : public vector<int> {};
+ // }
+ // }
+ // should have "std::vector<int>" as base class (whereas the type of the base class is
+ // "std::vector<T>").
+ const QStringList &baseScope = it.value()->scope();
+ if (!baseScope.isEmpty()) {
+ const int lastSep = baseClassName.lastIndexOf(u"::"_s);
+ if (lastSep >= 0)
+ baseClassName.remove(0, lastSep + u"::"_s.size());
+ baseClassName.prepend(u"::"_s);
+ baseClassName.prepend(baseScope.join(u"::"_s));
+ }
+ return {baseClassName, it.value()};
+}
+
+// Add a base class to the current class from CXCursor_CXXBaseSpecifier
+void BuilderPrivate::addBaseClass(const CXCursor &cursor)
+{
+ Q_ASSERT(clang_getCursorKind(cursor) == CXCursor_CXXBaseSpecifier);
+ const auto access = accessPolicy(clang_getCXXAccessSpecifier(cursor));
+ const auto baseClass = getBaseClass(clang_getCursorType(cursor));
+ m_currentClass->addBaseClass({baseClass.first, baseClass.second, access});
+}
+
+void BuilderPrivate::setFileName(const CXCursor &cursor, _CodeModelItem *item)
+{
+ const SourceRange range = getCursorRange(cursor);
+ QString file = m_baseVisitor->getFileName(range.first.file);
+ if (!file.isEmpty()) { // Has been observed to be 0 for invalid locations
+ item->setFileName(QDir::cleanPath(file));
+ item->setStartPosition(int(range.first.line), int(range.first.column));
+ item->setEndPosition(int(range.second.line), int(range.second.column));
+ }
+}
+
+Builder::Builder()
+{
+ d = new BuilderPrivate(this);
+}
+
+Builder::~Builder()
+{
+ delete d;
+}
+
+static QString baseName(QString path)
+{
+ qsizetype lastSlash = path.lastIndexOf(u'/');
+#ifdef Q_OS_WIN
+ if (lastSlash < 0)
+ lastSlash = path.lastIndexOf(u'\\');
+#endif
+ if (lastSlash > 0)
+ path.remove(0, lastSlash + 1);
+ return path;
+}
+
+const char * BuilderPrivate::specialSystemHeaderReason(BuilderPrivate::SpecialSystemHeader sh)
+{
+ static const QHash<SpecialSystemHeader, const char *> mapping {
+ {SpecialSystemHeader::OpenGL, "OpenGL"},
+ {SpecialSystemHeader::Types, "types"},
+ {SpecialSystemHeader::WhiteListed, "white listed"},
+ {SpecialSystemHeader::WhiteListedPath, "white listed path"}
+ };
+ return mapping.value(sh, "");
+}
+
+bool BuilderPrivate::visitHeader(const QString &fileName) const
+{
+ auto it = m_systemHeaders.find(fileName);
+ if (it == m_systemHeaders.end()) {
+ it = m_systemHeaders.insert(fileName, specialSystemHeader(fileName));
+ if (ReportHandler::isDebug(ReportHandler::MediumDebug)) {
+ const QString &name = QDir::toNativeSeparators(fileName);
+ if (it.value() == SpecialSystemHeader::None) {
+ qCInfo(lcShiboken, "Skipping system header %s", qPrintable(name));
+ } else {
+ qCInfo(lcShiboken, "Parsing system header %s (%s)",
+ qPrintable(name), specialSystemHeaderReason(it.value()));
+ }
+ }
+ }
+ return it.value() != SpecialSystemHeader::None;
+}
+
+BuilderPrivate::SpecialSystemHeader
+ BuilderPrivate::specialSystemHeader(const QString &fileName) const
+{
+ // Resolve OpenGL typedefs although the header is considered a system header.
+ const QString baseName = clang::baseName(fileName);
+ if (baseName == u"gl.h"
+ || baseName == u"gl2.h"
+ || baseName == u"gl3.h"
+ || baseName == u"gl31.h"
+ || baseName == u"gl32.h"
+ || baseName == u"stdint.h" // Windows: int32_t, uint32_t
+ || baseName == u"stddef.h") { // size_t`
+ return SpecialSystemHeader::OpenGL;
+ }
+
+ switch (clang::platform()) {
+ case Platform::Unix:
+ if (fileName == u"/usr/include/stdlib.h"
+ || baseName == u"types.h"
+ || baseName == u"stdint-intn.h" // int32_t
+ || baseName == u"stdint-uintn.h") { // uint32_t
+ return SpecialSystemHeader::Types;
+ }
+ break;
+ case Platform::macOS:
+ // Parse the following system headers to get the correct typdefs for types like
+ // int32_t, which are used in the macOS implementation of OpenGL framework.
+ // They are installed under /Applications/Xcode.app/Contents/Developer/Platforms...
+ if (baseName == u"gltypes.h"
+ || fileName.contains(u"/usr/include/_types")
+ || fileName.contains(u"/usr/include/sys/_types")) {
+ return SpecialSystemHeader::Types;
+ }
+ break;
+ default:
+ break;
+ }
+
+ // When building against system Qt (as it happens with yocto / Boot2Qt), the Qt headers are
+ // considered system headers by clang_Location_isInSystemHeader, and shiboken will not
+ // process them. We need to explicitly process them by checking against the list of
+ // include paths that were passed to shiboken's --force-process-system-include-paths option
+ // or specified via the <system-include> xml tag.
+ if (m_forceProcessSystemIncludes.contains(baseName))
+ return SpecialSystemHeader::WhiteListed;
+
+ if (std::any_of(m_forceProcessSystemIncludePaths.cbegin(),
+ m_forceProcessSystemIncludePaths.cend(),
+ [fileName](const QString &p) { return fileName.startsWith(p); })) {
+ return SpecialSystemHeader::WhiteListedPath;
+ }
+
+ return SpecialSystemHeader::None;
+}
+
+bool Builder::visitLocation(const QString &fileName, LocationType locationType) const
+{
+ return locationType != LocationType::System || d->visitHeader(fileName);
+}
+
+void Builder::setForceProcessSystemIncludes(const QStringList &systemIncludes)
+{
+ for (const auto &i : systemIncludes) {
+ QFileInfo fi(i);
+ if (fi.exists() && fi.isDir())
+ d->m_forceProcessSystemIncludePaths.append(i);
+ else
+ d->m_forceProcessSystemIncludes.append(i);
+ }
+}
+
+FileModelItem Builder::dom() const
+{
+ Q_ASSERT(!d->m_scopeStack.isEmpty());
+ auto rootScope = d->m_scopeStack.constFirst();
+ rootScope->purgeClassDeclarations();
+ return std::dynamic_pointer_cast<_FileModelItem>(rootScope);
+}
+
+static QString msgOutOfOrder(const CXCursor &cursor, const char *expectedScope)
+{
+ return getCursorKindName(cursor.kind) + u' '
+ + getCursorSpelling(cursor) + u" encountered outside "_s
+ + QLatin1StringView(expectedScope) + u'.';
+}
+
+static CodeModel::ClassType codeModelClassTypeFromCursor(CXCursorKind kind)
+{
+ CodeModel::ClassType result = CodeModel::Class;
+ if (kind == CXCursor_UnionDecl)
+ result = CodeModel::Union;
+ else if (kind == CXCursor_StructDecl)
+ result = CodeModel::Struct;
+ return result;
+}
+
+static NamespaceType namespaceType(const CXCursor &cursor)
+{
+ if (clang_Cursor_isAnonymous(cursor))
+ return NamespaceType::Anonymous;
+#if CINDEX_VERSION_MAJOR > 0 || CINDEX_VERSION_MINOR >= 59
+ if (clang_Cursor_isInlineNamespace(cursor))
+ return NamespaceType::Inline;
+#endif
+ return NamespaceType::Default;
+}
+
+static QString enumType(const CXCursor &cursor)
+{
+ QString name = getCursorSpelling(cursor); // "enum Foo { v1, v2 };"
+ if (name.contains(u"unnamed enum")) // Clang 16.0
+ return {};
+ if (name.isEmpty()) {
+ // PYSIDE-1228: For "typedef enum { v1, v2 } Foo;", type will return
+ // "Foo" as expected. Care must be taken to exclude real anonymous enums.
+ name = getTypeName(clang_getCursorType(cursor));
+ if (name.contains(u"(unnamed") // Clang 12.0.1
+ || name.contains(u"(anonymous")) { // earlier
+ name.clear();
+ }
+ }
+ return name;
+}
+
+BaseVisitor::StartTokenResult Builder::startToken(const CXCursor &cursor)
+{
+ switch (cursor.kind) {
+ case CXCursor_CXXAccessSpecifier:
+ d->m_currentFunctionType = CodeModel::Normal;
+ break;
+ case CXCursor_AnnotateAttr: {
+ const QString annotation = getCursorSpelling(cursor);
+ if (annotation == u"qt_slot")
+ d->m_currentFunctionType = CodeModel::Slot;
+ else if (annotation == u"qt_signal")
+ d->m_currentFunctionType = CodeModel::Signal;
+ else
+ d->m_currentFunctionType = CodeModel::Normal;
+ }
+ break;
+ case CXCursor_CXXBaseSpecifier:
+ if (!d->m_currentClass) {
+ const Diagnostic d(msgOutOfOrder(cursor, "class"), cursor, CXDiagnostic_Error);
+ qWarning() << d;
+ appendDiagnostic(d);
+ return Error;
+ }
+ d->addBaseClass(cursor);
+ break;
+ case CXCursor_ClassDecl:
+ case CXCursor_UnionDecl:
+ case CXCursor_StructDecl:
+ if (d->m_withinFriendDecl || clang_isCursorDefinition(cursor) == 0
+ || !d->addClass(cursor, codeModelClassTypeFromCursor(cursor.kind))) {
+ return Skip;
+ }
+ break;
+ case CXCursor_ClassTemplate:
+ case CXCursor_ClassTemplatePartialSpecialization:
+ if (d->m_withinFriendDecl || clang_isCursorDefinition(cursor) == 0
+ || !d->addClass(cursor, CodeModel::Class)) {
+ return Skip;
+ }
+ d->m_currentClass->setName(d->m_currentClass->name() + "<>"_L1);
+ d->m_scope.back() += "<>"_L1;
+ break;
+ case CXCursor_EnumDecl: {
+ QString name = enumType(cursor);
+ EnumKind kind = CEnum;
+ if (name.isEmpty()) {
+ kind = AnonymousEnum;
+ name = "enum_"_L1 + QString::number(++d->m_anonymousEnumCount);
+#if !CLANG_NO_ENUMDECL_ISSCOPED
+ } else if (clang_EnumDecl_isScoped(cursor) != 0) {
+#else
+ } else if (clang_EnumDecl_isScoped4(this, cursor) != 0) {
+#endif
+ kind = EnumClass;
+ }
+ d->m_currentEnum.reset(new _EnumModelItem(d->m_model, name));
+ d->setFileName(cursor, d->m_currentEnum.get());
+ d->m_currentEnum->setScope(d->m_scope);
+ d->m_currentEnum->setEnumKind(kind);
+ if (clang_getCursorAvailability(cursor) == CXAvailability_Deprecated)
+ d->m_currentEnum->setDeprecated(true);
+ const auto enumType = fullyResolveType(clang_getEnumDeclIntegerType(cursor));
+ d->m_currentEnum->setSigned(isSigned(enumType.kind));
+ d->m_currentEnum->setUnderlyingType(getTypeName(enumType));
+ if (std::dynamic_pointer_cast<_ClassModelItem>(d->m_scopeStack.back()))
+ d->m_currentEnum->setAccessPolicy(accessPolicy(clang_getCXXAccessSpecifier(cursor)));
+ }
+ break;
+ case CXCursor_EnumConstantDecl: {
+ const QString name = getCursorSpelling(cursor);
+ if (!d->m_currentEnum) {
+ const Diagnostic d(msgOutOfOrder(cursor, "enum"), cursor, CXDiagnostic_Error);
+ qWarning() << d;
+ appendDiagnostic(d);
+ return Error;
+ }
+ EnumValue enumValue;
+ if (d->m_currentEnum->isSigned())
+ enumValue.setValue(clang_getEnumConstantDeclValue(cursor));
+ else
+ enumValue.setUnsignedValue(clang_getEnumConstantDeclUnsignedValue(cursor));
+ auto enumConstant = std::make_shared<_EnumeratorModelItem>(d->m_model, name);
+ enumConstant->setStringValue(d->cursorValueExpression(this, cursor));
+ enumConstant->setValue(enumValue);
+ if (clang_getCursorAvailability(cursor) == CXAvailability_Deprecated)
+ enumConstant->setDeprecated(true);
+ d->m_currentEnum->addEnumerator(enumConstant);
+ }
+ break;
+ case CXCursor_VarDecl:
+ // static class members are seen as CXCursor_VarDecl
+ if (isClassOrNamespaceCursor(clang_getCursorSemanticParent(cursor))) {
+ d->addField(cursor);
+ d->m_currentField->setStatic(true);
+ }
+ break;
+ case CXCursor_FieldDecl:
+ d->addField(cursor);
+ break;
+ case CXCursor_FriendDecl:
+ d->m_withinFriendDecl = true;
+ break;
+ case CXCursor_CompoundStmt: // Function bodies
+ return Skip;
+ case CXCursor_Constructor:
+ case CXCursor_Destructor: // Note: Also use clang_CXXConstructor_is..Constructor?
+ case CXCursor_CXXMethod:
+ case CXCursor_ConversionFunction:
+ // Member functions of other classes can be declared to be friends.
+ // Skip inline member functions outside class, only go by declarations inside class
+ if (d->m_withinFriendDecl || !withinClassDeclaration(cursor))
+ return Skip;
+ d->m_currentFunction = d->createMemberFunction(cursor, false);
+ d->m_scopeStack.back()->addFunction(d->m_currentFunction);
+ break;
+ // Not fully supported, currently, seen as normal function
+ // Note: May appear inside class (member template) or outside (free template).
+ case CXCursor_FunctionTemplate: {
+ const CXCursor semParent = clang_getCursorSemanticParent(cursor);
+ if (isClassCursor(semParent)) {
+ if (semParent == clang_getCursorLexicalParent(cursor)) {
+ d->m_currentFunction = d->createMemberFunction(cursor, true);
+ d->m_scopeStack.back()->addFunction(d->m_currentFunction);
+ break;
+ }
+ return Skip; // inline member functions outside class
+ }
+ }
+ d->m_currentFunction = d->createFunction(cursor, CodeModel::Normal, true);
+ d->setFileName(cursor, d->m_currentFunction.get());
+ d->m_scopeStack.back()->addFunction(d->m_currentFunction);
+ break;
+ case CXCursor_FunctionDecl:
+ // Free functions or functions completely defined within "friend" (class
+ // operators). Note: CXTranslationUnit_SkipFunctionBodies must be off for
+ // clang_isCursorDefinition() to work here.
+ if (!d->m_withinFriendDecl || clang_isCursorDefinition(cursor) != 0) {
+ auto scope = d->m_scopeStack.size() - 1; // enclosing class
+ if (d->m_withinFriendDecl) {
+ // Friend declaration: go back to namespace or file scope.
+ for (--scope; d->m_scopeStack.at(scope)->kind() == _CodeModelItem::Kind_Class; --scope) {
+ }
+ }
+ d->m_currentFunction = d->createFunction(cursor, CodeModel::Normal, false);
+ d->m_currentFunction->setHiddenFriend(d->m_withinFriendDecl);
+ d->m_scopeStack.at(scope)->addFunction(d->m_currentFunction);
+ }
+ break;
+ case CXCursor_Namespace: {
+ const auto type = namespaceType(cursor);
+ if (type == NamespaceType::Anonymous)
+ return Skip;
+ const QString name = getCursorSpelling(cursor);
+ const auto parentNamespaceItem = std::dynamic_pointer_cast<_NamespaceModelItem>(d->m_scopeStack.back());
+ if (!parentNamespaceItem) {
+ const QString message = msgOutOfOrder(cursor, "namespace")
+ + u" (current scope: "_s + d->m_scopeStack.back()->name() + u')';
+ const Diagnostic d(message, cursor, CXDiagnostic_Error);
+ qWarning() << d;
+ appendDiagnostic(d);
+ return Error;
+ }
+ // Treat namespaces separately to allow for extending namespaces
+ // in subsequent modules.
+ NamespaceModelItem namespaceItem = parentNamespaceItem->findNamespace(name);
+ namespaceItem.reset(new _NamespaceModelItem(d->m_model, name));
+ d->setFileName(cursor, namespaceItem.get());
+ namespaceItem->setScope(d->m_scope);
+ namespaceItem->setType(type);
+ parentNamespaceItem->addNamespace(namespaceItem);
+ d->pushScope(namespaceItem);
+ }
+ break;
+ case CXCursor_ParmDecl:
+ // Skip in case of nested CXCursor_ParmDecls in case one parameter is a function pointer
+ // and function pointer typedefs.
+ if (!d->m_currentArgument && d->m_currentFunction) {
+ const QString name = getCursorSpelling(cursor);
+ d->m_currentArgument.reset(new _ArgumentModelItem(d->m_model, name));
+ const auto type = clang_getCursorType(cursor);
+ d->m_currentArgument->setScopeResolution(hasScopeResolution(type));
+ d->m_currentArgument->setType(d->createTypeInfo(type));
+ d->m_currentFunction->addArgument(d->m_currentArgument);
+ QString defaultValueExpression = d->cursorValueExpression(this, cursor);
+ if (!defaultValueExpression.isEmpty()) {
+ d->m_currentArgument->setDefaultValueExpression(defaultValueExpression);
+ d->m_currentArgument->setDefaultValue(true);
+ }
+ } else {
+ return Skip;
+ }
+ break;
+ case CXCursor_TemplateTypeParameter:
+ case CXCursor_NonTypeTemplateParameter: {
+ const TemplateParameterModelItem tItem = cursor.kind == CXCursor_TemplateTemplateParameter
+ ? d->createTemplateParameter(cursor) : d->createNonTypeTemplateParameter(cursor);
+ // Apply to function/member template?
+ if (d->m_currentFunction) {
+ d->m_currentFunction->setTemplateParameters(d->m_currentFunction->templateParameters() << tItem);
+ } else if (d->m_currentTemplateTypeAlias) {
+ d->m_currentTemplateTypeAlias->addTemplateParameter(tItem);
+ } else if (d->m_currentClass) { // Apply to class
+ const QString &tplParmName = tItem->name();
+ if (Q_UNLIKELY(!insertTemplateParameterIntoClassName(tplParmName, d->m_currentClass)
+ || !insertTemplateParameterIntoClassName(tplParmName, &d->m_scope.back()))) {
+ const QString message = "Error inserting template parameter \""_L1 + tplParmName
+ + "\" into "_L1 + d->m_currentClass->name();
+ const Diagnostic d(message, cursor, CXDiagnostic_Error);
+ qWarning() << d;
+ appendDiagnostic(d);
+ return Error;
+ }
+ d->m_currentClass->setTemplateParameters(d->m_currentClass->templateParameters() << tItem);
+ }
+ }
+ break;
+ case CXCursor_TypeAliasTemplateDecl:
+ d->startTemplateTypeAlias(cursor);
+ break;
+ case CXCursor_TypeAliasDecl: // May contain nested CXCursor_TemplateTypeParameter
+ if (!d->m_currentTemplateTypeAlias) {
+ const CXType type = clang_getCanonicalType(clang_getCursorType(cursor));
+ if (type.kind > CXType_Unexposed)
+ d->addTypeDef(cursor, type);
+ return Skip;
+ } else {
+ d->endTemplateTypeAlias(cursor);
+ }
+ break;
+ case CXCursor_TypedefDecl: {
+ auto underlyingType = clang_getTypedefDeclUnderlyingType(cursor);
+ d->addTypeDef(cursor, underlyingType);
+ // For "typedef enum/struct {} Foo;", skip the enum/struct
+ // definition nested into the typedef (PYSIDE-1228).
+ if (underlyingType.kind == CXType_Elaborated)
+ return Skip;
+ }
+ break;
+ // Using declarations look as follows:
+ // 1) Normal, non-template case ("using QObject::parent"): UsingDeclaration, TypeRef
+ // 2) Simple template case ("using QList::append()"): UsingDeclaration, TypeRef "QList<T>"
+ // 3) Template case with parameters ("using QList<T>::append()"):
+ // UsingDeclaration, TemplateRef "QList", TypeRef "T"
+ case CXCursor_TemplateRef:
+ if (d->m_withinUsingDeclaration && d->m_usingTypeRef.isEmpty())
+ d->m_usingTypeRef = getCursorSpelling(cursor);
+ break;
+ case CXCursor_TypeRef:
+ if (d->m_withinUsingDeclaration && d->m_usingTypeRef.isEmpty())
+ d->m_usingTypeRef = d->getBaseClass(clang_getCursorType(cursor)).first;
+ break;
+ case CXCursor_CXXFinalAttr:
+ if (d->m_currentFunction)
+ d->m_currentFunction->setAttribute(FunctionAttribute::Final);
+ else if (d->m_currentClass)
+ d->m_currentClass->setFinal(true);
+ break;
+ case CXCursor_CXXOverrideAttr:
+ if (d->m_currentFunction)
+ d->m_currentFunction->setAttribute(FunctionAttribute::Override);
+ break;
+ case CXCursor_StaticAssert:
+ // Check for Q_PROPERTY() (see PySide6/global.h.in for an explanation
+ // how it is defined, and qdoc).
+ if (clang_isDeclaration(cursor.kind) && d->m_currentClass) {
+ auto snippet = getCodeSnippet(cursor);
+ const auto length = snippet.size();
+ if (length > 12 && *snippet.rbegin() == ')'
+ && snippet.compare(0, 11, "Q_PROPERTY(") == 0) {
+ const QString qProperty = QString::fromUtf8(snippet.data() + 11, length - 12);
+ d->m_currentClass->addPropertyDeclaration(qProperty);
+ }
+ }
+ break;
+ // UsingDeclaration: consists of a TypeRef (base) and OverloadedDeclRef (member name)
+ case CXCursor_UsingDeclaration:
+ if (d->m_currentClass)
+ d->m_withinUsingDeclaration = true;
+ break;
+ case CXCursor_OverloadedDeclRef:
+ if (d->m_withinUsingDeclaration && !d->m_usingTypeRef.isEmpty()) {
+ QString member = getCursorSpelling(cursor);
+ if (member == d->m_currentClass->name())
+ member = d->m_usingTypeRef; // Overloaded member is Constructor, use base
+ const auto ap = accessPolicy(clang_getCXXAccessSpecifier(cursor));
+ d->m_currentClass->addUsingMember(d->m_usingTypeRef, member, ap);
+ }
+ break;
+ default:
+ break;
+ }
+ return BaseVisitor::Recurse;
+}
+
+bool Builder::endToken(const CXCursor &cursor)
+{
+ switch (cursor.kind) {
+ case CXCursor_UnionDecl:
+ case CXCursor_ClassDecl:
+ case CXCursor_StructDecl:
+ case CXCursor_ClassTemplate:
+ case CXCursor_ClassTemplatePartialSpecialization:
+ d->popScope();
+ // Continue in outer class after leaving inner class?
+ if (auto lastClass = std::dynamic_pointer_cast<_ClassModelItem>(d->m_scopeStack.back()))
+ d->m_currentClass = lastClass;
+ else
+ d->m_currentClass.reset();
+ d->m_currentFunctionType = CodeModel::Normal;
+ break;
+ case CXCursor_EnumDecl:
+ if (d->m_currentEnum)
+ d->m_scopeStack.back()->addEnum(d->m_currentEnum);
+ d->m_currentEnum.reset();
+ break;
+ case CXCursor_FriendDecl:
+ d->m_withinFriendDecl = false;
+ break;
+ case CXCursor_VarDecl:
+ case CXCursor_FieldDecl:
+ d->m_currentField.reset();
+ break;
+ case CXCursor_Constructor:
+ d->qualifyConstructor(cursor);
+ if (d->m_currentFunction) {
+ d->m_currentFunction->_determineType();
+ d->m_currentFunction.reset();
+ }
+ break;
+ case CXCursor_Destructor:
+ case CXCursor_CXXMethod:
+ case CXCursor_FunctionDecl:
+ case CXCursor_FunctionTemplate:
+ if (d->m_currentFunction) {
+ d->m_currentFunction->_determineType();
+ d->m_currentFunction.reset();
+ }
+ break;
+ case CXCursor_ConversionFunction:
+ if (d->m_currentFunction) {
+ d->m_currentFunction->setFunctionType(CodeModel::ConversionOperator);
+ d->m_currentFunction.reset();
+ }
+ break;
+ case CXCursor_Namespace:
+ d->popScope();
+ break;
+ case CXCursor_ParmDecl:
+ d->m_currentArgument.reset();
+ break;
+ case CXCursor_TypeAliasTemplateDecl:
+ d->m_currentTemplateTypeAlias.reset();
+ break;
+ case CXCursor_UsingDeclaration:
+ d->m_withinUsingDeclaration = false;
+ d->m_usingTypeRef.clear();
+ break;
+ default:
+ break;
+ }
+ return true;
+}
+
+} // namespace clang
diff --git a/sources/shiboken6/ApiExtractor/clangparser/clangbuilder.h b/sources/shiboken6/ApiExtractor/clangparser/clangbuilder.h
new file mode 100644
index 000000000..b2ec6d304
--- /dev/null
+++ b/sources/shiboken6/ApiExtractor/clangparser/clangbuilder.h
@@ -0,0 +1,37 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef CLANGBUILDER_H
+#define CLANGBUILDER_H
+
+#include "clangparser.h"
+
+#include <codemodel_fwd.h>
+
+namespace clang {
+
+class BuilderPrivate;
+
+class Builder : public BaseVisitor {
+public:
+ Q_DISABLE_COPY_MOVE(Builder)
+
+ Builder();
+ ~Builder();
+
+ void setForceProcessSystemIncludes(const QStringList &systemIncludes);
+
+ bool visitLocation(const QString &fileName, LocationType locationType) const override;
+
+ StartTokenResult startToken(const CXCursor &cursor) override;
+ bool endToken(const CXCursor &cursor) override;
+
+ FileModelItem dom() const;
+
+private:
+ BuilderPrivate *d;
+};
+
+} // namespace clang
+
+#endif // CLANGBUILDER_H
diff --git a/sources/shiboken6/ApiExtractor/clangparser/clangdebugutils.cpp b/sources/shiboken6/ApiExtractor/clangparser/clangdebugutils.cpp
new file mode 100644
index 000000000..3c002da9c
--- /dev/null
+++ b/sources/shiboken6/ApiExtractor/clangparser/clangdebugutils.cpp
@@ -0,0 +1,175 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "clangdebugutils.h"
+#include "clangutils.h"
+
+#include <QtCore/QDebug>
+#include <QtCore/QString>
+
+#ifndef QT_NO_DEBUG_STREAM
+
+#ifdef Q_OS_WIN
+const char pathSep = '\\';
+#else
+const char pathSep = '/';
+#endif
+
+static const char *baseName(const char *fileName)
+{
+ const char *b = std::strrchr(fileName, pathSep);
+ return b ? b + 1 : fileName;
+}
+
+QDebug operator<<(QDebug s, const CXString &cs)
+{
+ s << clang_getCString(cs);
+ return s;
+}
+
+QDebug operator<<(QDebug s, CXCursorKind cursorKind) // Enum
+{
+ const CXString kindName = clang_getCursorKindSpelling(cursorKind);
+ s << kindName;
+ clang_disposeString(kindName);
+ return s;
+}
+
+static const char *accessSpecsStrings[]
+{
+ // CX_CXXInvalidAccessSpecifier, CX_CXXPublic, CX_CXXProtected, CX_CXXPrivate
+ "invalid", "public", "protected", "private"
+};
+
+QDebug operator<<(QDebug s, CX_CXXAccessSpecifier ac)
+{
+ s << accessSpecsStrings[ac];
+ return s;
+}
+
+struct formatCXTypeName
+{
+ explicit formatCXTypeName(const CXType &type) : m_type(type) {}
+
+ const CXType &m_type;
+};
+
+QDebug operator<<(QDebug debug, const formatCXTypeName &ft)
+{
+ CXString typeSpelling = clang_getTypeSpelling(ft.m_type);
+ debug << typeSpelling;
+ clang_disposeString(typeSpelling);
+ return debug;
+}
+
+QDebug operator<<(QDebug debug, const CXType &type)
+{
+ QDebugStateSaver saver(debug);
+ debug.nospace();
+ debug.noquote();
+ debug << "CXType(";
+ if (type.kind == CXType_Invalid) {
+ debug << "invalid)";
+ return debug;
+ }
+
+ debug << type.kind;
+ switch (type.kind) {
+ case CXType_Unexposed:
+ debug << " [unexposed]";
+ break;
+ case CXType_Elaborated:
+ debug << " [elaborated]";
+ break;
+ default:
+ break;
+ }
+ debug << ", " << formatCXTypeName(type) << ')';
+ return debug;
+}
+
+QDebug operator<<(QDebug debug, const CXCursor &cursor)
+{
+ QDebugStateSaver saver(debug);
+ debug.nospace();
+ debug.noquote();
+ const CXCursorKind kind = clang_getCursorKind(cursor);
+ debug << "CXCursor(";
+ if (kind >= CXCursor_FirstInvalid && kind <= CXCursor_LastInvalid) {
+ debug << "invalid)";
+ return debug;
+ }
+
+ const QString cursorSpelling = clang::getCursorSpelling(cursor);
+ debug << '"' << cursorSpelling << '"';
+ CXString cursorDisplay = clang_getCursorDisplayName(cursor);
+ if (const char *dpy = clang_getCString(cursorDisplay)) {
+ const QString display = QString::fromUtf8(dpy);
+ if (display != cursorSpelling)
+ debug << ", display=\"" << dpy << '"';
+ }
+ clang_disposeString(cursorDisplay);
+
+ debug << ", kind=" << kind;
+
+ const CXType type = clang_getCursorType(cursor);
+ switch (kind) {
+ case CXCursor_CXXAccessSpecifier:
+ debug << ", " << clang_getCXXAccessSpecifier(cursor);
+ break;
+ case CXCursor_CXXBaseSpecifier:
+ debug << ", inherits=\"" << clang::getCursorSpelling(clang_getTypeDeclaration(type)) << '"';
+ break;
+ case CXCursor_CXXMethod:
+ case CXCursor_FunctionDecl:
+ case CXCursor_ConversionFunction:
+ debug << ", result type=\""
+ << formatCXTypeName(clang_getCursorResultType(cursor)) << '"';
+ break;
+ case CXCursor_TypedefDecl:
+ debug << ", underlyingType=\""
+ << formatCXTypeName(clang_getTypedefDeclUnderlyingType(cursor)) << '"';
+ break;
+ default:
+ break;
+ }
+
+ debug << ", type=\"" << formatCXTypeName(type) << '"';
+ if (clang_Cursor_hasAttrs(cursor))
+ debug << ", [attrs]";
+
+ debug << ')';
+ return debug;
+}
+
+QDebug operator<<(QDebug s, const CXSourceLocation &location)
+{
+ QDebugStateSaver saver(s);
+ s.nospace();
+ CXFile file; // void *
+ unsigned line;
+ unsigned column;
+ unsigned offset;
+ clang_getExpansionLocation(location, &file, &line, &column, &offset);
+ const CXString cxFileName = clang_getFileName(file);
+ // Has been observed to be 0 for invalid locations
+ if (const char *cFileName = clang_getCString(cxFileName))
+ s << baseName(cFileName) << ':';
+ s << line << ':' << column;
+ clang_disposeString(cxFileName);
+ return s;
+}
+
+QDebug operator<<(QDebug s, const std::string_view &v)
+{
+ QDebugStateSaver saver(s);
+ s.nospace();
+ s.noquote();
+ s << '"';
+ for (auto c : v)
+ s << c;
+ s << '"';
+ return s;
+}
+
+#endif // !QT_NO_DEBUG_STREAM
diff --git a/sources/shiboken6/ApiExtractor/clangparser/clangdebugutils.h b/sources/shiboken6/ApiExtractor/clangparser/clangdebugutils.h
new file mode 100644
index 000000000..7aac8a575
--- /dev/null
+++ b/sources/shiboken6/ApiExtractor/clangparser/clangdebugutils.h
@@ -0,0 +1,26 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef CLANGDEBUGUTILS_H
+#define CLANGDEBUGUTILS_H
+
+#include <QtCore/qtclasshelpermacros.h>
+
+#include <clang-c/Index.h>
+
+#include <string_view>
+
+QT_FORWARD_DECLARE_CLASS(QDebug)
+QT_FORWARD_DECLARE_CLASS(QString)
+
+#ifndef QT_NO_DEBUG_STREAM
+QDebug operator<<(QDebug s, const CXString &cs);
+QDebug operator<<(QDebug s, CXCursorKind cursorKind);
+QDebug operator<<(QDebug s, CX_CXXAccessSpecifier ac);
+QDebug operator<<(QDebug s, const CXType &t);
+QDebug operator<<(QDebug s, const CXCursor &cursor);
+QDebug operator<<(QDebug s, const CXSourceLocation &location);
+QDebug operator<<(QDebug s, const std::string_view &v); // for code snippets
+#endif // !QT_NO_DEBUG_STREAM
+
+#endif // CLANGDEBUGUTILS_H
diff --git a/sources/shiboken6/ApiExtractor/clangparser/clangparser.cpp b/sources/shiboken6/ApiExtractor/clangparser/clangparser.cpp
new file mode 100644
index 000000000..6c0cf3ae2
--- /dev/null
+++ b/sources/shiboken6/ApiExtractor/clangparser/clangparser.cpp
@@ -0,0 +1,323 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "clangparser.h"
+#include "clangutils.h"
+#include "clangdebugutils.h"
+#include "compilersupport.h"
+
+#include <QtCore/QByteArrayList>
+#include <QtCore/QDebug>
+#include <QtCore/QDir>
+#include <QtCore/QFile>
+#include <QtCore/QScopedArrayPointer>
+#include <QtCore/QString>
+
+using namespace Qt::StringLiterals;
+
+namespace clang {
+
+QString SourceFileCache::getFileName(CXFile file)
+{
+ auto it = m_fileNameCache.find(file);
+ if (it == m_fileNameCache.end())
+ it = m_fileNameCache.insert(file, clang::getFileName(file));
+ return it.value();
+}
+
+std::string_view SourceFileCache::getCodeSnippet(const CXCursor &cursor,
+ QString *errorMessage)
+{
+ static const char empty[] = "";
+
+ if (errorMessage)
+ errorMessage->clear();
+
+ const SourceRange range = getCursorRange(cursor);
+ // Quick check for equal locations: Frequently happens if the code is
+ // the result of a macro expansion
+ if (range.first == range.second)
+ return std::string_view(empty, 0);
+
+ if (range.first.file != range.second.file) {
+ if (errorMessage)
+ *errorMessage = "Range spans several files"_L1;
+ return std::string_view(empty, 0);
+ }
+
+ auto it = m_fileBufferCache.find(range.first.file);
+ if (it == m_fileBufferCache.end()) {
+ const QString fileName = getFileName(range.first.file);
+ if (fileName.isEmpty()) {
+ if (errorMessage)
+ *errorMessage = "Range has no file"_L1;
+ return std::string_view(empty, 0);
+ }
+ QFile file(fileName);
+ if (!file.open(QIODevice::ReadOnly)) {
+ if (errorMessage) {
+ QTextStream str(errorMessage);
+ str << "Cannot open \"" << QDir::toNativeSeparators(fileName)
+ << "\": " << file.errorString();
+ }
+ return std::string_view(empty, 0);
+ }
+ it = m_fileBufferCache.insert(range.first.file, file.readAll());
+ }
+
+ const unsigned pos = range.first.offset;
+ const unsigned end = range.second.offset;
+ Q_ASSERT(end > pos);
+ const QByteArray &contents = it.value();
+ if (end >= unsigned(contents.size())) {
+ if (errorMessage) {
+ QTextStream str(errorMessage);
+ str << "Range end " << end << " is above size of \""
+ << QDir::toNativeSeparators(getFileName(range.first.file))
+ << "\" (" << contents.size() << ')';
+ }
+ return std::string_view(empty, 0);
+ }
+
+ return std::string_view(contents.constData() + pos, end - pos);
+}
+
+BaseVisitor::BaseVisitor() = default;
+BaseVisitor::~BaseVisitor() = default;
+
+bool BaseVisitor::visitLocation(const QString &, LocationType locationType) const
+{
+ return locationType != LocationType::System;
+}
+
+BaseVisitor::StartTokenResult BaseVisitor::cbHandleStartToken(const CXCursor &cursor)
+{
+ switch (cursor.kind) {
+ default:
+ break;
+ }
+
+ return startToken(cursor);
+}
+
+bool BaseVisitor::cbHandleEndToken(const CXCursor &cursor, StartTokenResult startResult)
+{
+ const bool result = startResult != Recurse || endToken(cursor);
+ switch (cursor.kind) {
+ default:
+ break;
+ }
+
+ return result;
+}
+
+std::string_view BaseVisitor::getCodeSnippet(const CXCursor &cursor)
+{
+ QString errorMessage;
+ const std::string_view result = m_fileCache.getCodeSnippet(cursor, &errorMessage);
+ if (result.empty() && !errorMessage.isEmpty()) {
+ QString message;
+ QTextStream str(&message);
+ str << "Unable to retrieve code snippet \"" << getCursorSpelling(cursor)
+ << "\": " << errorMessage;
+ appendDiagnostic(Diagnostic(message, cursor, CXDiagnostic_Error));
+ }
+ return result;
+}
+
+bool BaseVisitor::_handleVisitLocation(const CXSourceLocation &location)
+{
+ CXFile cxFile; // void *
+ unsigned line;
+ unsigned column;
+ unsigned offset;
+ clang_getExpansionLocation(location, &cxFile, &line, &column, &offset);
+
+ if (cxFile == m_currentCxFile) // Same file?
+ return m_visitCurrent;
+
+ const QString fileName = getFileName(cxFile);
+
+ LocationType locationType = LocationType::Unknown;
+ if (!fileName.isEmpty()) {
+ if (clang_Location_isFromMainFile(location) != 0)
+ locationType = LocationType::Main;
+ else if (clang_Location_isInSystemHeader(location) != 0)
+ locationType = LocationType::System;
+ else
+ locationType = LocationType::Other;
+ }
+
+ m_currentCxFile = cxFile;
+ m_visitCurrent = visitLocation(fileName, locationType);
+ return m_visitCurrent;
+}
+
+QString BaseVisitor::getCodeSnippetString(const CXCursor &cursor)
+{
+ const std::string_view result = getCodeSnippet(cursor);
+ return result.empty()
+ ? QString()
+ : QString::fromUtf8(result.data(), qsizetype(result.size()));
+}
+
+static CXChildVisitResult
+ visitorCallback(CXCursor cursor, CXCursor /* parent */, CXClientData clientData)
+{
+ auto *bv = reinterpret_cast<BaseVisitor *>(clientData);
+
+ const CXSourceLocation location = clang_getCursorLocation(cursor);
+ if (!bv->_handleVisitLocation(location))
+ return CXChildVisit_Continue;
+
+ const BaseVisitor::StartTokenResult startResult = bv->cbHandleStartToken(cursor);
+ switch (startResult) {
+ case clang::BaseVisitor::Error:
+ return CXChildVisit_Break;
+ case clang::BaseVisitor::Skip:
+ break;
+ case clang::BaseVisitor::Recurse:
+ clang_visitChildren(cursor, visitorCallback, clientData);
+ break;
+ }
+
+ if (!bv->cbHandleEndToken(cursor, startResult))
+ return CXChildVisit_Break;
+
+ return CXChildVisit_Continue;
+}
+
+BaseVisitor::Diagnostics BaseVisitor::diagnostics() const
+{
+ return m_diagnostics;
+}
+
+void BaseVisitor::setDiagnostics(const Diagnostics &d)
+{
+ m_diagnostics = d;
+}
+
+void BaseVisitor::appendDiagnostic(const Diagnostic &d)
+{
+ m_diagnostics.append(d);
+}
+
+static inline const char **byteArrayListToFlatArgV(const QByteArrayList &bl)
+{
+ const char **result = new const char *[bl.size() + 1];
+ result[bl.size()] = nullptr;
+ std::transform(bl.cbegin(), bl.cend(), result,
+ [] (const QByteArray &a) { return a.constData(); });
+ return result;
+}
+
+static QByteArray msgCreateTranslationUnit(const QByteArrayList &clangArgs, unsigned flags)
+{
+ QByteArray result = "clang_parseTranslationUnit2(0x";
+ result += QByteArray::number(flags, 16);
+ const auto count = clangArgs.size();
+ result += ", cmd[" + QByteArray::number(count) + "]=";
+ for (qsizetype i = 0; i < count; ++i) {
+ const QByteArray &arg = clangArgs.at(i);
+ if (i)
+ result += ' ';
+ const bool quote = arg.contains(' ') || arg.contains('(');
+ if (quote)
+ result += '"';
+ result += arg;
+ if (quote)
+ result += '"';
+ }
+ result += ')';
+ return result;
+}
+
+static CXTranslationUnit createTranslationUnit(CXIndex index,
+ const QByteArrayList &args,
+ bool addCompilerSupportArguments,
+ LanguageLevel level,
+ unsigned flags = 0)
+{
+ // courtesy qdoc
+ const unsigned defaultFlags = CXTranslationUnit_Incomplete;
+
+ static const QByteArrayList defaultArgs = {
+#ifndef Q_OS_WIN
+ "-fPIC",
+#endif
+#ifdef Q_OS_MACOS
+ "-Wno-expansion-to-defined", // Workaround for warnings in Darwin stdlib, see
+ // https://github.com/darlinghq/darling/issues/204
+#endif
+ "-Wno-constant-logical-operand",
+ "-x",
+ "c++" // Treat .h as C++, not C
+ };
+
+ QByteArrayList clangArgs;
+ if (addCompilerSupportArguments) {
+ clangArgs += emulatedCompilerOptions(level);
+ clangArgs += defaultArgs;
+ }
+ clangArgs += detectVulkan();
+ clangArgs += args;
+ QScopedArrayPointer<const char *> argv(byteArrayListToFlatArgV(clangArgs));
+ qDebug().noquote().nospace() << msgCreateTranslationUnit(clangArgs, flags);
+
+ CXTranslationUnit tu;
+ CXErrorCode err = clang_parseTranslationUnit2(index, nullptr, argv.data(),
+ clangArgs.size(), nullptr, 0,
+ defaultFlags | flags, &tu);
+ if (err || !tu) {
+ qWarning().noquote().nospace() << "Could not parse "
+ << clangArgs.constLast().constData() << ", error code: " << err;
+ return nullptr;
+ }
+ return tu;
+}
+
+/* clangFlags are flags to clang_parseTranslationUnit2() such as
+ * CXTranslationUnit_KeepGoing (from CINDEX_VERSION_MAJOR/CINDEX_VERSION_MINOR 0.35)
+ */
+
+bool parse(const QByteArrayList &clangArgs, bool addCompilerSupportArguments,
+ LanguageLevel level, unsigned clangFlags, BaseVisitor &bv)
+{
+ CXIndex index = clang_createIndex(0 /* excludeDeclarationsFromPCH */,
+ 1 /* displayDiagnostics */);
+ if (!index) {
+ qWarning() << "clang_createIndex() failed!";
+ return false;
+ }
+
+ CXTranslationUnit translationUnit =
+ createTranslationUnit(index, clangArgs, addCompilerSupportArguments,
+ level, clangFlags);
+ if (!translationUnit)
+ return false;
+
+ CXCursor rootCursor = clang_getTranslationUnitCursor(translationUnit);
+
+ clang_visitChildren(rootCursor, visitorCallback, reinterpret_cast<CXClientData>(&bv));
+
+ QList<Diagnostic> diagnostics = getDiagnostics(translationUnit);
+ diagnostics.append(bv.diagnostics());
+ bv.setDiagnostics(diagnostics);
+
+ const bool ok = maxSeverity(diagnostics) < CXDiagnostic_Error;
+ if (!ok) {
+ QDebug debug = qWarning();
+ debug.noquote();
+ debug.nospace();
+ debug << "Errors in "
+ << QDir::toNativeSeparators(QFile::decodeName(clangArgs.constLast())) << ":\n";
+ for (const Diagnostic &diagnostic : std::as_const(diagnostics))
+ debug << diagnostic << '\n';
+ }
+
+ clang_disposeTranslationUnit(translationUnit);
+ clang_disposeIndex(index);
+ return ok;
+}
+
+} // namespace clang
diff --git a/sources/shiboken6/ApiExtractor/clangparser/clangparser.h b/sources/shiboken6/ApiExtractor/clangparser/clangparser.h
new file mode 100644
index 000000000..22e0a50cd
--- /dev/null
+++ b/sources/shiboken6/ApiExtractor/clangparser/clangparser.h
@@ -0,0 +1,88 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef CLANGPARSER_H
+#define CLANGPARSER_H
+
+#include <clang-c/Index.h>
+
+#include <QtCore/QByteArrayList>
+#include <QtCore/QHash>
+#include <QtCore/QString>
+#include <QtCore/QList>
+
+#include <string_view>
+#include <utility>
+
+enum class LanguageLevel;
+
+namespace clang {
+
+struct Diagnostic;
+
+class SourceFileCache {
+public:
+ std::string_view getCodeSnippet(const CXCursor &cursor, QString *errorMessage = nullptr);
+ QString getFileName(CXFile file);
+
+private:
+ using FileBufferCache = QHash<CXFile, QByteArray>;
+ using FileNameCache = QHash<CXFile, QString>;
+
+ FileBufferCache m_fileBufferCache;
+ FileNameCache m_fileNameCache;
+};
+
+enum class LocationType
+{
+ Main, // Main header parsed for bindings
+ Other, // A header parsed for bindings
+ System, // A system header
+ Unknown // Clang internal
+};
+
+class BaseVisitor {
+ Q_DISABLE_COPY_MOVE(BaseVisitor)
+public:
+ using Diagnostics = QList<Diagnostic>;
+
+ enum StartTokenResult { Error, Skip, Recurse };
+
+ BaseVisitor();
+ virtual ~BaseVisitor();
+
+ // Whether location should be visited.
+ // defaults to clang_Location_isFromMainFile()
+ virtual bool visitLocation(const QString &fileName, LocationType locationType) const;
+
+ virtual StartTokenResult startToken(const CXCursor &cursor) = 0;
+ virtual bool endToken(const CXCursor &cursor) = 0;
+
+ StartTokenResult cbHandleStartToken(const CXCursor &cursor);
+ bool cbHandleEndToken(const CXCursor &cursor, StartTokenResult startResult);
+
+ QString getFileName(CXFile file) { return m_fileCache.getFileName(file); }
+ std::string_view getCodeSnippet(const CXCursor &cursor);
+ QString getCodeSnippetString(const CXCursor &cursor);
+
+ Diagnostics diagnostics() const;
+ void setDiagnostics(const Diagnostics &d);
+ void appendDiagnostic(const Diagnostic &d);
+
+ // For usage by the parser
+ bool _handleVisitLocation( const CXSourceLocation &location);
+
+private:
+ SourceFileCache m_fileCache;
+ Diagnostics m_diagnostics;
+ CXFile m_currentCxFile{};
+ bool m_visitCurrent = true;
+};
+
+bool parse(const QByteArrayList &clangArgs,
+ bool addCompilerSupportArguments,
+ LanguageLevel level, unsigned clangFlags, BaseVisitor &ctx);
+
+} // namespace clang
+
+#endif // !CLANGPARSER_H
diff --git a/sources/shiboken6/ApiExtractor/clangparser/clangutils.cpp b/sources/shiboken6/ApiExtractor/clangparser/clangutils.cpp
new file mode 100644
index 000000000..1651e09ec
--- /dev/null
+++ b/sources/shiboken6/ApiExtractor/clangparser/clangutils.cpp
@@ -0,0 +1,320 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "clangutils.h"
+
+#include <QtCore/QDebug>
+#include <QtCore/QDir>
+#include <QtCore/QHashFunctions>
+#include <QtCore/QProcess>
+
+#include <string_view>
+
+bool operator==(const CXCursor &c1, const CXCursor &c2) noexcept
+{
+ return c1.kind == c2.kind
+ && c1.xdata == c2.xdata
+ && std::equal(c1.data, c1.data + sizeof(c1.data) / sizeof(c1.data[0]), c2.data);
+}
+
+size_t qHash(const CXCursor &c, size_t seed) noexcept
+{
+ return qHashMulti(seed, c.kind, c.xdata, c.data[0], c.data[1], c.data[2]);
+}
+
+bool operator==(const CXType &t1, const CXType &t2) noexcept
+{
+ return t1.kind == t2.kind && t1.data[0] == t2.data[0]
+ && t1.data[1] == t2.data[1];
+}
+
+size_t qHash(const CXType &ct, size_t seed) noexcept
+{
+ return qHashMulti(seed, ct.kind, ct.data[0], ct.data[1]);
+}
+
+namespace clang {
+
+SourceLocation getExpansionLocation(const CXSourceLocation &location)
+{
+ SourceLocation result;
+ clang_getExpansionLocation(location, &result.file, &result.line, &result.column, &result.offset);
+ return result;
+}
+
+QString getFileName(CXFile file)
+{
+ QString result;
+ const CXString cxFileName = clang_getFileName(file);
+ // Has been observed to be 0 for invalid locations
+ if (const char *cFileName = clang_getCString(cxFileName))
+ result = QString::fromUtf8(cFileName);
+ clang_disposeString(cxFileName);
+ return result;
+}
+
+SourceLocation getCursorLocation(const CXCursor &cursor)
+{
+ const CXSourceRange extent = clang_getCursorExtent(cursor);
+ return getExpansionLocation(clang_getRangeStart(extent));
+}
+
+CXString getFileNameFromLocation(const CXSourceLocation &location)
+{
+ CXFile file;
+ unsigned line;
+ unsigned column;
+ unsigned offset;
+ clang_getExpansionLocation(location, &file, &line, &column, &offset);
+ return clang_getFileName(file);
+}
+
+SourceRange getCursorRange(const CXCursor &cursor)
+{
+ const CXSourceRange extent = clang_getCursorExtent(cursor);
+ return std::make_pair(getExpansionLocation(clang_getRangeStart(extent)),
+ getExpansionLocation(clang_getRangeEnd(extent)));
+}
+
+QString getCursorKindName(CXCursorKind cursorKind)
+{
+ CXString kindName = clang_getCursorKindSpelling(cursorKind);
+ const QString result = QString::fromUtf8(clang_getCString(kindName));
+ clang_disposeString(kindName);
+ return result;
+}
+
+QString getCursorSpelling(const CXCursor &cursor)
+{
+ CXString cursorSpelling = clang_getCursorSpelling(cursor);
+ const QString result = QString::fromUtf8(clang_getCString(cursorSpelling));
+ clang_disposeString(cursorSpelling);
+ return result;
+}
+
+QString getCursorDisplayName(const CXCursor &cursor)
+{
+ CXString displayName = clang_getCursorDisplayName(cursor);
+ const QString result = QString::fromUtf8(clang_getCString(displayName));
+ clang_disposeString(displayName);
+ return result;
+}
+
+static inline bool isBuiltinType(CXTypeKind kind)
+{
+ return kind >= CXType_FirstBuiltin && kind <= CXType_LastBuiltin;
+}
+
+// Resolve elaborated types occurring with clang 16
+static CXType resolveElaboratedType(const CXType &type)
+{
+ if (!isBuiltinType(type.kind)) {
+ CXCursor decl = clang_getTypeDeclaration(type);
+ auto resolvedType = clang_getCursorType(decl);
+ if (resolvedType.kind != CXType_Invalid && resolvedType.kind != type.kind)
+ return resolvedType;
+ }
+ return type;
+}
+
+// Resolve typedefs
+static CXType resolveTypedef(const CXType &type)
+{
+ auto result = type;
+ while (result.kind == CXType_Typedef) {
+ auto decl = clang_getTypeDeclaration(result);
+ auto resolved = clang_getTypedefDeclUnderlyingType(decl);
+ if (resolved.kind == CXType_Invalid)
+ break;
+ result = resolved;
+ }
+ return result;
+}
+
+// Fully resolve a type from elaborated & typedefs
+CXType fullyResolveType(const CXType &type)
+{
+ return resolveTypedef(resolveElaboratedType(type));
+}
+
+QString getTypeName(const CXType &type)
+{
+ CXString typeSpelling = clang_getTypeSpelling(type);
+ const QString result = QString::fromUtf8(clang_getCString(typeSpelling));
+ clang_disposeString(typeSpelling);
+ return result;
+}
+
+// Quick check for "::Type"
+bool hasScopeResolution(const CXType &type)
+{
+ CXString typeSpelling = clang_getTypeSpelling(type);
+ std::string_view spelling = clang_getCString(typeSpelling);
+ const bool result = spelling.compare(0, 2, "::") == 0
+ || spelling.find(" ::") != std::string::npos;
+ clang_disposeString(typeSpelling);
+ return result;
+}
+
+// Resolve elaborated types occurring with clang 16
+QString getResolvedTypeName(const CXType &type)
+{
+ return getTypeName(resolveElaboratedType(type));
+}
+
+Diagnostic::Diagnostic(const QString &m, const CXCursor &c, CXDiagnosticSeverity s)
+ : message(m), source(Other), severity(s)
+{
+ setLocation(getCursorLocation(c));
+}
+
+Diagnostic Diagnostic::fromCXDiagnostic(CXDiagnostic cd)
+{
+ Diagnostic result;
+ result.source = Clang;
+ CXString spelling = clang_getDiagnosticSpelling(cd);
+ result.message = QString::fromUtf8(clang_getCString(spelling));
+ clang_disposeString(spelling);
+ result.severity = clang_getDiagnosticSeverity(cd);
+ result.setLocation(getExpansionLocation(clang_getDiagnosticLocation(cd)));
+
+ CXDiagnosticSet childDiagnostics = clang_getChildDiagnostics(cd);
+ if (const unsigned childCount = clang_getNumDiagnosticsInSet(childDiagnostics)) {
+ result.childMessages.reserve(int(childCount));
+ const unsigned format = clang_defaultDiagnosticDisplayOptions();
+ for (unsigned i = 0; i < childCount; ++i) {
+ CXDiagnostic childDiagnostic = clang_getDiagnosticInSet(childDiagnostics, i);
+ CXString cdm = clang_formatDiagnostic(childDiagnostic, format);
+ result.childMessages.append(QString::fromUtf8(clang_getCString(cdm)));
+ clang_disposeString(cdm);
+ clang_disposeDiagnostic(childDiagnostic);
+ }
+ }
+
+ return result;
+}
+
+void Diagnostic::setLocation(const SourceLocation &sourceLocation)
+{
+ file = getFileName(sourceLocation.file);
+ line = sourceLocation.line;
+ column = sourceLocation.column;
+ offset = sourceLocation.offset;
+}
+
+QList<Diagnostic> getDiagnostics(CXTranslationUnit tu)
+{
+ QList<Diagnostic> result;
+ const unsigned count = clang_getNumDiagnostics(tu);
+ result.reserve(int(count));
+ for (unsigned i = 0; i < count; ++i) {
+ const CXDiagnostic d = clang_getDiagnostic(tu, i);
+ result.append(Diagnostic::fromCXDiagnostic(d));
+ clang_disposeDiagnostic(d);
+ }
+ return result;
+}
+
+std::pair<qsizetype, qsizetype>
+ parseTemplateArgumentList(const QString &l,
+ const TemplateArgumentHandler &handler,
+ qsizetype from)
+{
+ const auto ltPos = l.indexOf(u'<', from);
+ if (ltPos == - 1)
+ return std::make_pair(-1, -1);
+ auto startPos = ltPos + 1;
+ int level = 1;
+ for (qsizetype p = startPos, end = l.size(); p < end; ) {
+ const char c = l.at(p).toLatin1();
+ switch (c) {
+ case ',':
+ case '>':
+ handler(level, QStringView{l}.mid(startPos, p - startPos).trimmed());
+ ++p;
+ if (c == '>') {
+ if (--level == 0)
+ return std::make_pair(ltPos, p);
+ // Skip over next ',': "a<b<c,d>,e>"
+ for (; p < end && (l.at(p).isSpace() || l.at(p) == u','); ++p) {}
+ }
+ startPos = p;
+ break;
+ case '<':
+ handler(level, QStringView{l}.mid(startPos, p - startPos).trimmed());
+ ++level;
+ startPos = ++p;
+ break;
+ default:
+ ++p;
+ break;
+ }
+ }
+ return std::make_pair(-1, -1);
+}
+
+CXDiagnosticSeverity maxSeverity(const QList<Diagnostic> &ds)
+{
+ CXDiagnosticSeverity result = CXDiagnostic_Ignored;
+ for (const Diagnostic& d : ds) {
+ if (d.severity > result)
+ result = d.severity;
+ }
+ return result;
+}
+
+#ifndef QT_NO_DEBUG_STREAM
+
+QDebug operator<<(QDebug s, const SourceLocation &l)
+{
+ QDebugStateSaver saver(s);
+ s.nospace();
+ s.noquote();
+ s << QDir::toNativeSeparators(clang::getFileName(l.file)) << ':' << l.line;
+ if (l.column)
+ s << ':' << l.column;
+ return s;
+}
+
+// Roughly follow g++ format:
+// file.cpp:214:37: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]
+QDebug operator<<(QDebug s, const Diagnostic &d)
+{
+ QDebugStateSaver saver(s);
+ s.nospace();
+ s.noquote();
+ s << d.file << ':'<< d.line << ':' << d.column << ": ";
+ switch (d.severity) {
+ case CXDiagnostic_Ignored:
+ s << "ignored";
+ break;
+ case CXDiagnostic_Note:
+ s << "note";
+ break;
+ case CXDiagnostic_Warning:
+ s << "warning";
+ break;
+ case CXDiagnostic_Error:
+ s << "error";
+ break;
+ case CXDiagnostic_Fatal:
+ s << "fatal";
+ break;
+ }
+ s << ": " << d.message;
+
+ if (d.source != Diagnostic::Clang)
+ s << " [other]";
+
+ if (const auto childMessagesCount = d.childMessages.size()) {
+ s << '\n';
+ for (qsizetype i = 0; i < childMessagesCount; ++i)
+ s << " " << d.childMessages.at(i) << '\n';
+ }
+
+ return s;
+}
+
+#endif // QT_NO_DEBUG_STREAM
+
+} // namespace clang
diff --git a/sources/shiboken6/ApiExtractor/clangparser/clangutils.h b/sources/shiboken6/ApiExtractor/clangparser/clangutils.h
new file mode 100644
index 000000000..fbbf95f1b
--- /dev/null
+++ b/sources/shiboken6/ApiExtractor/clangparser/clangutils.h
@@ -0,0 +1,108 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef CLANGUTILS_H
+#define CLANGUTILS_H
+
+#include <clang-c/Index.h>
+#include <QtCore/QString>
+#include <QtCore/QStringList>
+#include <QtCore/QtCompare>
+#include <QtCore/QList>
+
+#include <functional>
+#include <utility>
+
+QT_FORWARD_DECLARE_CLASS(QDebug)
+
+bool operator==(const CXCursor &c1, const CXCursor &c2) noexcept;
+size_t qHash(const CXCursor &c, size_t seed = 0) noexcept;
+
+bool operator==(const CXType &t1, const CXType &t2) noexcept;
+size_t qHash(const CXType &ct, size_t seed = 0) noexcept;
+
+namespace clang {
+
+QString getCursorKindName(CXCursorKind cursorKind);
+QString getCursorSpelling(const CXCursor &cursor);
+QString getCursorDisplayName(const CXCursor &cursor);
+QString getTypeName(const CXType &type);
+bool hasScopeResolution(const CXType &type);
+CXType fullyResolveType(const CXType &type);
+QString getResolvedTypeName(const CXType &type);
+inline QString getCursorTypeName(const CXCursor &cursor)
+ { return getTypeName(clang_getCursorType(cursor)); }
+inline QString getCursorResultTypeName(const CXCursor &cursor)
+ { return getTypeName(clang_getCursorResultType(cursor)); }
+
+inline bool isCursorValid(const CXCursor &c)
+{
+ return c.kind < CXCursor_FirstInvalid || c.kind > CXCursor_LastInvalid;
+}
+
+QString getFileName(CXFile file); // Uncached,see BaseVisitor for a cached version
+
+struct SourceLocation
+{
+ bool equals(const SourceLocation &rhs) const;
+
+ CXFile file = nullptr;
+ unsigned line = 0;
+ unsigned column = 0;
+ unsigned offset = 0;
+
+ friend constexpr bool comparesEqual(const SourceLocation &lhs,
+ const SourceLocation &rhs) noexcept
+ {
+ return lhs.file == rhs.file && lhs.offset == rhs.offset;
+ }
+ Q_DECLARE_EQUALITY_COMPARABLE_LITERAL_TYPE(SourceLocation)
+};
+
+SourceLocation getExpansionLocation(const CXSourceLocation &location);
+
+using SourceRange = std::pair<SourceLocation, SourceLocation>;
+
+SourceLocation getCursorLocation(const CXCursor &cursor);
+CXString getFileNameFromLocation(const CXSourceLocation &location);
+SourceRange getCursorRange(const CXCursor &cursor);
+
+struct Diagnostic {
+ enum Source { Clang, Other };
+
+ Diagnostic() = default;
+ // Clang
+ static Diagnostic fromCXDiagnostic(CXDiagnostic cd);
+ // Other
+ explicit Diagnostic(const QString &m, const CXCursor &c, CXDiagnosticSeverity s = CXDiagnostic_Warning);
+ void setLocation(const SourceLocation &);
+
+ QString message;
+ QStringList childMessages;
+ QString file;
+ unsigned line = 0;
+ unsigned column = 0;
+ unsigned offset = 0;
+ Source source = Clang;
+ CXDiagnosticSeverity severity = CXDiagnostic_Warning;
+};
+
+QList<Diagnostic> getDiagnostics(CXTranslationUnit tu);
+CXDiagnosticSeverity maxSeverity(const QList<Diagnostic> &ds);
+
+// Parse a template argument list "a<b<c,d>,e>" and invoke a handler
+// with each match (level and string). Return begin and end of the list.
+using TemplateArgumentHandler = std::function<void (int, QStringView)>;
+
+std::pair<qsizetype, qsizetype>
+ parseTemplateArgumentList(const QString &l,
+ const TemplateArgumentHandler &handler,
+ qsizetype from = 0);
+
+#ifndef QT_NO_DEBUG_STREAM
+QDebug operator<<(QDebug, const SourceLocation &);
+QDebug operator<<(QDebug, const Diagnostic &);
+#endif // QT_NO_DEBUG_STREAM
+} // namespace clang
+
+#endif // CLANGUTILS_H
diff --git a/sources/shiboken6/ApiExtractor/clangparser/compilersupport.cpp b/sources/shiboken6/ApiExtractor/clangparser/compilersupport.cpp
new file mode 100644
index 000000000..20224020b
--- /dev/null
+++ b/sources/shiboken6/ApiExtractor/clangparser/compilersupport.cpp
@@ -0,0 +1,456 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "compilersupport.h"
+#include "header_paths.h"
+#include "clangutils.h"
+
+#include <reporthandler.h>
+
+#include "qtcompat.h"
+
+#include <QtCore/QDebug>
+#include <QtCore/QDir>
+#include <QtCore/QFile>
+#include <QtCore/QFileInfo>
+#include <QtCore/QProcess>
+#include <QtCore/QStandardPaths>
+#include <QtCore/QStringList>
+#include <QtCore/QVersionNumber>
+
+#include <clang-c/Index.h>
+
+#include <algorithm>
+#include <iterator>
+
+using namespace Qt::StringLiterals;
+
+namespace clang {
+
+QVersionNumber libClangVersion()
+{
+ return QVersionNumber(CINDEX_VERSION_MAJOR, CINDEX_VERSION_MINOR);
+}
+
+static Compiler _compiler =
+#if defined (Q_CC_CLANG)
+ Compiler::Clang;
+#elif defined (Q_CC_MSVC)
+ Compiler::Msvc;
+#else
+ Compiler::Gpp;
+#endif
+
+Compiler compiler() { return _compiler; }
+
+bool setCompiler(const QString &name)
+{
+ bool result = true;
+ if (name == u"msvc")
+ _compiler = Compiler::Msvc;
+ else if (name == u"g++")
+ _compiler = Compiler::Gpp;
+ else if (name == u"clang")
+ _compiler = Compiler::Clang;
+ else
+ result = false;
+ return result;
+}
+
+QString _compilerPath; // Pre-defined compiler path (from command line)
+
+const QString &compilerPath()
+{
+ return _compilerPath;
+}
+
+void setCompilerPath(const QString &name)
+{
+ _compilerPath = name;
+}
+
+static Platform _platform =
+#if defined (Q_OS_DARWIN)
+ Platform::macOS;
+#elif defined (Q_OS_WIN)
+ Platform::Windows;
+#else
+ Platform::Unix;
+#endif
+
+Platform platform() { return _platform; }
+
+bool setPlatform(const QString &name)
+{
+ bool result = true;
+ if (name == u"windows")
+ _platform = Platform::Windows;
+ else if (name == u"darwin")
+ _platform = Platform::macOS;
+ else if (name == u"unix")
+ _platform = Platform::Unix;
+ else
+ result = false;
+ return result;
+}
+
+// 3/2024: Use a recent MSVC2022 for libclang 18.X
+static QByteArray msvcCompatVersion()
+{
+ return libClangVersion() >= QVersionNumber(0, 64) ? "19.39"_ba : "19.26"_ba;
+}
+
+static bool runProcess(const QString &program, const QStringList &arguments,
+ QByteArray *stdOutIn = nullptr, QByteArray *stdErrIn = nullptr)
+{
+ QProcess process;
+ process.start(program, arguments, QProcess::ReadWrite);
+ if (!process.waitForStarted()) {
+ qCWarning(lcShiboken).noquote().nospace() << "Unable to start "
+ << process.program() << ": " << process.errorString();
+ return false;
+ }
+ process.closeWriteChannel();
+ const bool finished = process.waitForFinished();
+ const QByteArray stdErr = process.readAllStandardError();
+ if (stdErrIn)
+ *stdErrIn = stdErr;
+ if (stdOutIn)
+ *stdOutIn = process.readAllStandardOutput();
+
+ if (!finished) {
+ qCWarning(lcShiboken).noquote().nospace() << process.program() << " timed out: " << stdErr;
+ process.kill();
+ return false;
+ }
+
+ if (process.exitStatus() != QProcess::NormalExit) {
+ qCWarning(lcShiboken).noquote().nospace() << process.program() << " crashed: " << stdErr;
+ return false;
+ }
+
+ if (process.exitCode() != 0) {
+ qCWarning(lcShiboken).noquote().nospace() << process.program() << " exited "
+ << process.exitCode() << ": " << stdErr;
+ return false;
+ }
+
+ return true;
+}
+
+static QByteArray frameworkPath() { return QByteArrayLiteral(" (framework directory)"); }
+
+static void filterHomebrewHeaderPaths(HeaderPaths &headerPaths)
+{
+ QByteArray homebrewPrefix = qgetenv("HOMEBREW_OPT");
+
+ // If HOMEBREW_OPT is found we assume that the build is happening
+ // inside a brew environment, which means we need to filter out
+ // the -isystem flags added by the brew clang shim. This is needed
+ // because brew passes the Qt include paths as system include paths
+ // and because our parser ignores system headers, Qt classes won't
+ // be found and thus compilation errors will occur.
+ if (homebrewPrefix.isEmpty())
+ return;
+
+ qCInfo(lcShiboken) << "Found HOMEBREW_OPT with value:" << homebrewPrefix
+ << "Assuming homebrew build environment.";
+
+ HeaderPaths::iterator it = headerPaths.begin();
+ while (it != headerPaths.end()) {
+ if (it->path.startsWith(homebrewPrefix)) {
+ qCInfo(lcShiboken) << "Filtering out homebrew include path: "
+ << it->path;
+ it = headerPaths.erase(it);
+ } else {
+ ++it;
+ }
+ }
+}
+
+// Determine g++'s internal include paths from the output of
+// g++ -E -x c++ - -v </dev/null
+// Output looks like:
+// #include <...> search starts here:
+// /usr/local/include
+// /System/Library/Frameworks (framework directory)
+// End of search list.
+static HeaderPaths gppInternalIncludePaths(const QString &compiler)
+{
+ HeaderPaths result;
+ QStringList arguments{u"-E"_s, u"-x"_s, u"c++"_s, u"-"_s, u"-v"_s};
+ QByteArray stdOut;
+ QByteArray stdErr;
+ if (!runProcess(compiler, arguments, &stdOut, &stdErr))
+ return result;
+ const QByteArrayList stdErrLines = stdErr.split('\n');
+ bool isIncludeDir = false;
+
+ if (ReportHandler::isDebug(ReportHandler::MediumDebug))
+ qCInfo(lcShiboken()).noquote().nospace()
+ << "gppInternalIncludePaths:\n compiler: " << compiler
+ << "\n stdOut: " << stdOut
+ << "\n stdErr: " << stdErr;
+
+ for (const QByteArray &line : stdErrLines) {
+ if (isIncludeDir) {
+ if (line.startsWith(QByteArrayLiteral("End of search list"))) {
+ isIncludeDir = false;
+ } else {
+ HeaderPath headerPath{line.trimmed(), HeaderType::System};
+ if (headerPath.path.endsWith(frameworkPath())) {
+ headerPath.type = HeaderType::FrameworkSystem;
+ headerPath.path.truncate(headerPath.path.size() - frameworkPath().size());
+ }
+ result.append(headerPath);
+ }
+ } else if (line.startsWith(QByteArrayLiteral("#include <...> search starts here"))) {
+ isIncludeDir = true;
+ }
+ }
+
+ if (platform() == Platform::macOS)
+ filterHomebrewHeaderPaths(result);
+
+ return result;
+}
+
+// Detect Vulkan as supported from Qt 5.10 by checking the environment variables.
+QByteArrayList detectVulkan()
+{
+ static const char *vulkanVariables[] = {"VULKAN_SDK", "VK_SDK_PATH"};
+ for (const char *vulkanVariable : vulkanVariables) {
+ if (qEnvironmentVariableIsSet(vulkanVariable)) {
+ const auto option = QByteArrayLiteral("-isystem")
+ + qgetenv(vulkanVariable)
+ + QByteArrayLiteral("/include");
+ return {option};
+ }
+ }
+ return {};
+}
+
+// For MSVC, we set the MS compatibility version and let Clang figure out its own
+// options and include paths.
+// For the others, we pass "-nostdinc" since libclang tries to add it's own system
+// include paths, which together with the clang compiler paths causes some clash
+// which causes std types not being found and construct -I/-F options from the
+// include paths of the host compiler.
+
+static QByteArray noStandardIncludeOption() { return QByteArrayLiteral("-nostdinc"); }
+
+// The clang builtin includes directory is used to find the definitions for
+// intrinsic functions and builtin types. It is necessary to use the clang
+// includes to prevent redefinition errors. The default toolchain includes
+// should be picked up automatically by clang without specifying
+// them implicitly.
+
+// Besides g++/Linux, as of MSVC 19.28.29334, MSVC needs clang includes
+// due to PYSIDE-1433, LLVM-47099
+
+static bool needsClangBuiltinIncludes()
+{
+ return platform() != Platform::macOS;
+}
+
+static QString queryLlvmConfigDir(const QString &arg)
+{
+ static const QString llvmConfig = QStandardPaths::findExecutable(u"llvm-config"_s);
+ if (llvmConfig.isEmpty())
+ return {};
+ QByteArray stdOut;
+ if (!runProcess(llvmConfig, QStringList{arg}, &stdOut))
+ return {};
+ const QString path = QFile::decodeName(stdOut.trimmed());
+ if (!QFileInfo::exists(path)) {
+ qCWarning(lcShiboken, R"(%s: "%s" as returned by llvm-config "%s" does not exist.)",
+ __FUNCTION__, qPrintable(QDir::toNativeSeparators(path)), qPrintable(arg));
+ return {};
+ }
+ return path;
+}
+
+static QString findClangLibDir()
+{
+ for (const char *envVar : {"LLVM_INSTALL_DIR", "CLANG_INSTALL_DIR"}) {
+ if (qEnvironmentVariableIsSet(envVar)) {
+ const QString path = QFile::decodeName(qgetenv(envVar)) + u"/lib"_s;
+ if (QFileInfo::exists(path))
+ return path;
+ qCWarning(lcShiboken, "%s: %s as pointed to by %s does not exist.",
+ __FUNCTION__, qPrintable(path), envVar);
+ }
+ }
+ return queryLlvmConfigDir(u"--libdir"_s);
+}
+
+static QString findClangBuiltInIncludesDir()
+{
+ // Find the include directory of the highest version.
+ const QString clangPathLibDir = findClangLibDir();
+ if (!clangPathLibDir.isEmpty()) {
+ QString candidate;
+ QString clangDirName = clangPathLibDir + u"/clang"_s;
+ // PYSIDE-2769: llvm-config --libdir may report /usr/lib64 on manylinux_2_28_x86_64
+ // whereas the includes are under /usr/lib/clang/../include.
+ if (!QFileInfo::exists(clangDirName) && clangPathLibDir.endsWith("64"_L1)) {
+ const QString fallback = clangPathLibDir.sliced(0, clangPathLibDir.size() - 2);
+ clangDirName = fallback + u"/clang"_s;
+ qCWarning(lcShiboken, "%s: Falling back from %s to %s.",
+ __FUNCTION__, qPrintable(clangPathLibDir), qPrintable(fallback));
+ }
+
+ QVersionNumber lastVersionNumber(1, 0, 0);
+ QDir clangDir(clangDirName);
+ const QFileInfoList versionDirs =
+ clangDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
+ if (versionDirs.isEmpty())
+ qCWarning(lcShiboken, "%s: No subdirectories found in %s.",
+ __FUNCTION__, qPrintable(clangDirName));
+ for (const QFileInfo &fi : versionDirs) {
+ const QString fileName = fi.fileName();
+ if (fileName.at(0).isDigit()) {
+ const QVersionNumber versionNumber = QVersionNumber::fromString(fileName);
+ if (!versionNumber.isNull() && versionNumber > lastVersionNumber) {
+ candidate = fi.absoluteFilePath();
+ lastVersionNumber = versionNumber;
+ }
+ }
+ }
+ if (!candidate.isEmpty())
+ return candidate + "/include"_L1;
+ }
+ return queryLlvmConfigDir(u"--includedir"_s);
+}
+
+QString compilerFromCMake()
+{
+#ifdef CMAKE_CXX_COMPILER
+ return QString::fromLocal8Bit(CMAKE_CXX_COMPILER);
+#else
+ return {};
+#endif
+}
+
+// Return a compiler suitable for determining the internal include paths
+static QString compilerFromCMake(const QString &defaultCompiler)
+{
+ if (!compilerPath().isEmpty())
+ return compilerPath();
+ // Exclude macOS since cmakeCompiler returns the full path instead of the
+ // /usr/bin/clang shim, which results in the default SDK sysroot path
+ // missing (PYSIDE-1032)
+ if (platform() == Platform::macOS)
+ return defaultCompiler;
+ QString cmakeCompiler = compilerFromCMake();
+ if (cmakeCompiler.isEmpty())
+ return defaultCompiler;
+ QFileInfo fi(cmakeCompiler);
+ // Should be absolute by default, but a user may specify -DCMAKE_CXX_COMPILER=cl.exe
+ if (fi.isRelative())
+ return cmakeCompiler;
+ if (fi.exists())
+ return fi.absoluteFilePath();
+ // The compiler may not exist in case something like icecream or
+ // a non-standard-path was used on the build machine. Check
+ // the executable.
+ cmakeCompiler = QStandardPaths::findExecutable(fi.fileName());
+ return cmakeCompiler.isEmpty() ? defaultCompiler : cmakeCompiler;
+}
+
+static void appendClangBuiltinIncludes(HeaderPaths *p)
+{
+ const QString clangBuiltinIncludesDir =
+ QDir::toNativeSeparators(findClangBuiltInIncludesDir());
+ if (clangBuiltinIncludesDir.isEmpty()) {
+ qCWarning(lcShiboken, "Unable to locate Clang's built-in include directory "
+ "(neither by checking the environment variables LLVM_INSTALL_DIR, CLANG_INSTALL_DIR "
+ " nor running llvm-config). This may lead to parse errors.");
+ } else {
+ qCInfo(lcShiboken, "CLANG v%d.%d, builtins includes directory: %s",
+ CINDEX_VERSION_MAJOR, CINDEX_VERSION_MINOR,
+ qPrintable(clangBuiltinIncludesDir));
+ p->append(HeaderPath{QFile::encodeName(clangBuiltinIncludesDir),
+ HeaderType::System});
+ }
+}
+
+// Returns clang options needed for emulating the host compiler
+QByteArrayList emulatedCompilerOptions(LanguageLevel level)
+{
+ QByteArrayList result;
+ HeaderPaths headerPaths;
+ switch (compiler()) {
+ case Compiler::Msvc:
+ result.append("-fms-compatibility-version="_ba + msvcCompatVersion());
+ if (level < LanguageLevel::Cpp20)
+ result.append("-fdelayed-template-parsing"_ba);
+ result.append(QByteArrayLiteral("-Wno-microsoft-enum-value"));
+ result.append("/Zc:__cplusplus"_ba);
+ // Fix yvals_core.h: STL1000: Unexpected compiler version, expected Clang 7 or newer (MSVC2017 update)
+ result.append(QByteArrayLiteral("-D_ALLOW_COMPILER_AND_STL_VERSION_MISMATCH"));
+ if (needsClangBuiltinIncludes())
+ appendClangBuiltinIncludes(&headerPaths);
+ break;
+ case Compiler::Clang:
+ headerPaths.append(gppInternalIncludePaths(compilerFromCMake(u"clang++"_s)));
+ result.append(noStandardIncludeOption());
+ break;
+ case Compiler::Gpp:
+ if (needsClangBuiltinIncludes())
+ appendClangBuiltinIncludes(&headerPaths);
+
+ // Append the c++ include paths since Clang is unable to find
+ // <type_traits> etc (g++ 11.3).
+ const HeaderPaths gppPaths = gppInternalIncludePaths(compilerFromCMake(u"g++"_s));
+ for (const HeaderPath &h : gppPaths) {
+ if (h.path.contains("c++") || h.path.contains("sysroot"))
+ headerPaths.append(h);
+ }
+ break;
+ }
+
+ std::transform(headerPaths.cbegin(), headerPaths.cend(),
+ std::back_inserter(result), HeaderPath::includeOption);
+ return result;
+}
+
+LanguageLevel emulatedCompilerLanguageLevel()
+{
+ return LanguageLevel::Cpp17;
+}
+
+struct LanguageLevelMapping
+{
+ const char *option;
+ LanguageLevel level;
+};
+
+static const LanguageLevelMapping languageLevelMapping[] =
+{
+ {"c++11", LanguageLevel::Cpp11},
+ {"c++14", LanguageLevel::Cpp14},
+ {"c++17", LanguageLevel::Cpp17},
+ {"c++20", LanguageLevel::Cpp20},
+ {"c++1z", LanguageLevel::Cpp1Z}
+};
+
+const char *languageLevelOption(LanguageLevel l)
+{
+ for (const LanguageLevelMapping &m : languageLevelMapping) {
+ if (m.level == l)
+ return m.option;
+ }
+ return nullptr;
+}
+
+LanguageLevel languageLevelFromOption(const char *o)
+{
+ for (const LanguageLevelMapping &m : languageLevelMapping) {
+ if (!strcmp(m.option, o))
+ return m.level;
+ }
+ return LanguageLevel::Default;
+}
+
+} // namespace clang
diff --git a/sources/shiboken6/ApiExtractor/clangparser/compilersupport.h b/sources/shiboken6/ApiExtractor/clangparser/compilersupport.h
new file mode 100644
index 000000000..f1d63b7c3
--- /dev/null
+++ b/sources/shiboken6/ApiExtractor/clangparser/compilersupport.h
@@ -0,0 +1,56 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef COMPILERSUPPORT_H
+#define COMPILERSUPPORT_H
+
+#include <QtCore/QByteArrayList>
+
+QT_FORWARD_DECLARE_CLASS(QVersionNumber)
+QT_FORWARD_DECLARE_CLASS(QString)
+
+enum class LanguageLevel {
+ Default,
+ Cpp11,
+ Cpp14,
+ Cpp17,
+ Cpp20,
+ Cpp1Z
+};
+
+enum class Compiler {
+ Msvc,
+ Gpp,
+ Clang
+};
+
+enum class Platform {
+ Unix,
+ Windows,
+ macOS
+};
+
+namespace clang {
+QVersionNumber libClangVersion();
+
+QByteArrayList emulatedCompilerOptions(LanguageLevel level);
+LanguageLevel emulatedCompilerLanguageLevel();
+
+const char *languageLevelOption(LanguageLevel l);
+LanguageLevel languageLevelFromOption(const char *);
+
+QByteArrayList detectVulkan();
+
+Compiler compiler();
+bool setCompiler(const QString &name);
+
+QString compilerFromCMake();
+
+const QString &compilerPath();
+void setCompilerPath(const QString &name);
+
+Platform platform();
+bool setPlatform(const QString &name);
+} // namespace clang
+
+#endif // COMPILERSUPPORT_H