// 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.h>
#include <QtCore/qdir.h>
#include <QtCore/qhash.h>
#include <QtCore/qmap.h>
#include <QtCore/qstring.h>
#include <QtCore/qstack.h>
#include <QtCore/qlist.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) const;

    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 = std::make_shared<_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 auto 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;
    case CXCursor_CXXMethod:
#ifdef CLANG_HAS_ASSIGNMENT_OPERATOR_CHECK
        if (clang_CXXMethod_isCopyAssignmentOperator(cursor) != 0)
            result = CodeModel::AssignmentOperator;
        else if (clang_CXXMethod_isMoveAssignmentOperator(cursor) != 0)
            result = CodeModel::MoveAssignmentOperator;
#endif
        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;
    auto end = t.indexOf(u'<');
    if (end == -1)
        end = t.indexOf(u'(');
    if (end == -1)
        end = t.size();
    qsizetype lastPos = 0;
    while (true) {
        const auto 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);
}

static TypeCategory typeCategoryFromClang(CXTypeKind k)
{
    switch (k) {
    case CXType_Void:
        return TypeCategory::Void;
    case CXType_Enum:
        return TypeCategory::Enum;
    case CXType_Pointer:
    case CXType_BlockPointer:
        return TypeCategory::Pointer;
    case CXType_FunctionNoProto:
    case CXType_FunctionProto:
        return TypeCategory::Function;
    default:
        break;
    }
    if (k >= CXType_FirstBuiltin && k <= CXType_LastBuiltin)
        return TypeCategory::Builtin;
    return TypeCategory::Other;
}

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.setTypeCategory(TypeCategory::Pointer);
            result.setFunctionPointer(true);
            for (int a = 0; a < argCount; ++a)
                result.addArgument(createTypeInfoUncached(clang_getArgType(pointeeType, unsigned(a)),
                                                          cacheable));
            return result;
        }
    }

    TypeInfo typeInfo;
    typeInfo.setTypeCategory(typeCategoryFromClang(clang_getCanonicalType(type).kind));

    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);
    item->setAccessPolicy(accessPolicy(clang_getCXXAccessSpecifier(cursor)));
    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 = std::make_shared<_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 {};
    ++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 auto 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
{
    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::Linux:
    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:
    case Platform::iOS:
        // 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)
{
    // Skip inline code
    if ((cursor.kind >= CXCursor_FirstExpr && cursor.kind <= CXCursor_LastExpr)
        || (cursor.kind >= CXCursor_FirstStmt && cursor.kind <= CXCursor_LastStmt)) {
        return Skip;
    }

    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 = std::make_shared<_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(BuilderPrivate::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_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 = std::make_shared<_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 = std::make_shared<_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 = BuilderPrivate::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
