/*
  Copyright Kitware, Inc.

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      https://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

#include "Output.h"
#include "Options.h"
#include "Utils.h"

#include "llvm/Config/llvm-config.h"

#include "clang/AST/ASTContext.h"
#include "clang/AST/Attr.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/DeclFriend.h"
#include "clang/AST/DeclObjC.h"
#include "clang/AST/DeclOpenMP.h"
#include "clang/AST/DeclTemplate.h"
#include "clang/AST/Expr.h"
#include "clang/AST/ExprCXX.h"
#include "clang/AST/Mangle.h"
#include "clang/AST/NestedNameSpecifier.h"
#include "clang/AST/PrettyPrinter.h"
#include "clang/AST/RecordLayout.h"
#include "clang/Basic/FileManager.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/Specifiers.h"
#include "clang/Basic/TargetInfo.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Lex/Preprocessor.h"
#include "llvm/Support/raw_ostream.h"

#include <fstream>
#include <iomanip>
#include <iostream>
#include <queue>
#include <set>
#include <string>
#include <vector>

#if LLVM_VERSION_MAJOR >= 21
#  include "clang/AST/DeclOpenACC.h"
#endif

#if LLVM_VERSION_MAJOR >= 16
#  include <optional>
namespace cx {
template <typename T>
using optional = std::optional<T>;
}
#else
#  include <llvm/ADT/Optional.h>
namespace cx {
template <typename T>
using optional = llvm::Optional<T>;
}
#endif

#if LLVM_VERSION_MAJOR >= 16
namespace cx {
using FileEntryRef = clang::FileEntryRef;
using OptionalFileEntryRef = clang::OptionalFileEntryRef;
}
#elif LLVM_VERSION_MAJOR >= 12
namespace cx {
using FileEntryRef = clang::FileEntryRef;
using OptionalFileEntryRef = llvm::Optional<clang::FileEntryRef>;
}
#else
namespace cx {
using FileEntryRef = clang::FileEntry const*;
using OptionalFileEntryRef = clang::FileEntry const*;
}
#endif

#if LLVM_VERSION_MAJOR >= 22
namespace cx {
using NestedNameSpecifier = clang::NestedNameSpecifier;
NestedNameSpecifier const& deref(NestedNameSpecifier const& nns)
{
  return nns;
}
}
#else
namespace cx {
using NestedNameSpecifier = clang::NestedNameSpecifier*;
clang::NestedNameSpecifier const& deref(NestedNameSpecifier const& nns)
{
  return *nns;
}
}
#endif

#if LLVM_VERSION_MAJOR >= 18
#  define cx_ElaboratedTypeKeyword(x) clang::ElaboratedTypeKeyword::x
#  define cx_TagTypeKind(x) clang::TagTypeKind::x
#else
#  define cx_ElaboratedTypeKeyword(x) clang::ETK_##x
#  define cx_TagTypeKind(x) clang::TTK_##x
#endif

#if LLVM_VERSION_MAJOR < 16
#  define starts_with startswith
#endif

#if LLVM_VERSION_MAJOR < 18
#  define isPureVirtual isPure
#endif

class ASTVisitorBase
{
protected:
  clang::CompilerInstance& CI;
  clang::ASTContext const& CTX;
  llvm::raw_ostream& OS;

  ASTVisitorBase(clang::CompilerInstance& ci, clang::ASTContext const& ctx,
                 llvm::raw_ostream& os)
    : CI(ci)
    , CTX(ctx)
    , OS(os)
  {
  }

  // Represent cv qualifier state of one dump node.
  struct DumpQual
  {
  private:
    typedef void (DumpQual::*bool_type)() const;
    void bool_true() const {}

  public:
    bool IsConst;
    bool IsVolatile;
    bool IsRestrict;
    DumpQual()
      : IsConst(false)
      , IsVolatile(false)
      , IsRestrict(false)
    {
    }
    operator bool_type() const
    {
      return (this->IsConst || this->IsVolatile || this->IsRestrict)
        ? &DumpQual::bool_true
        : nullptr;
    }
    friend bool operator<(DumpQual const& l, DumpQual const& r)
    {
      if (!l.IsConst && r.IsConst) {
        return true;
      } else if (l.IsConst && !r.IsConst) {
        return false;
      } else if (!l.IsVolatile && r.IsVolatile) {
        return true;
      } else if (l.IsVolatile && !r.IsVolatile) {
        return false;
      } else if (!l.IsRestrict && r.IsRestrict) {
        return true;
      } else if (l.IsRestrict && !r.IsRestrict) {
        return false;
      } else {
        return false;
      }
    }
    friend llvm::raw_ostream& operator<<(llvm::raw_ostream& os,
                                         DumpQual const& dq)
    {
      return os << (dq.IsConst ? "c" : "") << (dq.IsVolatile ? "v" : "")
                << (dq.IsRestrict ? "r" : "");
    }
  };

  // Represent id of one dump node.
  struct DumpId
  {
  private:
    typedef void (DumpId::*bool_type)() const;
    void bool_true() const {}

  public:
    unsigned int Id;
    DumpQual Qual;
    DumpId()
      : Id(0)
      , Qual()
    {
    }
    DumpId(unsigned int id, DumpQual dq)
      : Id(id)
      , Qual(dq)
    {
    }
    operator bool_type() const
    {
      return this->Id != 0 ? &DumpId::bool_true : nullptr;
    }
    friend bool operator<(DumpId const& l, DumpId const& r)
    {
      if (l.Id < r.Id) {
        return true;
      } else if (l.Id > r.Id) {
        return false;
      } else {
        return l.Qual < r.Qual;
      }
    }
    friend llvm::raw_ostream& operator<<(llvm::raw_ostream& os,
                                         DumpId const& id)
    {
      return os << id.Id << id.Qual;
    }
  };

  // Record status of one AST node to be dumped.
  struct DumpNode
  {
    DumpNode()
      : Index()
      , Complete(false)
    {
    }

    // Index in nodes ordered by first encounter.
    DumpId Index;

    // Whether the node is to be traversed completely.
    bool Complete;
  };

// Report all decl nodes as unimplemented until overridden.
#define ABSTRACT_DECL(DECL)
#define DECL(CLASS, BASE)                                                     \
  void Output##CLASS##Decl(clang::CLASS##Decl const* d, DumpNode const* dn)   \
  {                                                                           \
    this->OutputUnimplementedDecl(d, dn);                                     \
  }
#include "clang/AST/DeclNodes.inc"

  void OutputUnimplementedDecl(clang::Decl const* d, DumpNode const* dn)
  {
    /* clang-format off */
    this->OS << "  <Unimplemented id=\"_" << dn->Index
             << "\" kind=\"" << encodeXML(d->getDeclKindName()) << "\"/>\n";
    /* clang-format on */
  }

// Report all type nodes as unimplemented until overridden.
#define ABSTRACT_TYPE(CLASS, BASE)
#define TYPE(CLASS, BASE)                                                     \
  void Output##CLASS##Type(clang::CLASS##Type const* t, DumpNode const* dn)   \
  {                                                                           \
    this->OutputUnimplementedType(t, dn);                                     \
  }
#if LLVM_VERSION_MAJOR >= 10
#  include "clang/AST/TypeNodes.inc"
#else
#  include "clang/AST/TypeNodes.def"
#endif

  void OutputUnimplementedType(clang::Type const* t, DumpNode const* dn)
  {
    /* clang-format off */
    this->OS << "  <Unimplemented id=\"_" << dn->Index
             << "\" type_class=\"" << encodeXML(t->getTypeClassName())
             << "\"/>\n";
    /* clang-format on */
  }

  std::string getNameOfFileEntryRef(cx::FileEntryRef f) const
  {
#if LLVM_VERSION_MAJOR >= 12
    return std::string(f.getName());
#else
    return std::string(f->getName());
#endif
  }
  cx::OptionalFileEntryRef getFileEntryRefForID(clang::FileID id) const
  {
#if LLVM_VERSION_MAJOR >= 12
    return this->CI.getSourceManager().getFileEntryRefForID(id);
#else
    return this->CI.getSourceManager().getFileEntryForID(id);
#endif
  }
};

class ASTVisitor : public ASTVisitorBase
{
  // Store a type to be visited, possibly as a record member.
  struct DumpType
  {
    DumpType()
      : Type()
      , Class(0)
    {
    }
    DumpType(clang::QualType t, clang::Type const* c = 0)
      : Type(t)
      , Class(c)
    {
    }
    friend bool operator<(DumpType const& l, DumpType const& r)
    {
      // Order by pointer value without discarding low-order
      // bits used to encode qualifiers.
      void const* lpp = &l.Type;
      void const* rpp = &r.Type;
      void const* lpv = *static_cast<void const* const*>(lpp);
      void const* rpv = *static_cast<void const* const*>(rpp);
      if (lpv < rpv) {
        return true;
      } else if (lpv > rpv) {
        return false;
      } else {
        return l.Class < r.Class;
      }
    }

    clang::QualType Type;
    clang::Type const* Class;
  };

  // Store an entry in the node traversal queue.
  struct QueueEntry
  {
    // Available node kinds.
    enum Kinds
    {
      KindQual,
      KindDecl,
      KindType
    };

    QueueEntry(DumpNode const* dn)
      : Kind(KindQual)
      , Decl(nullptr)
      , Type()
      , DN(dn)
    {
    }
    QueueEntry(clang::Decl const* d, DumpNode const* dn)
      : Kind(KindDecl)
      , Decl(d)
      , Type()
      , DN(dn)
    {
    }
    QueueEntry(DumpType t, DumpNode const* dn)
      : Kind(KindType)
      , Decl(nullptr)
      , Type(t)
      , DN(dn)
    {
    }

    // Kind of node at this entry.
    Kinds Kind;

    // The declaration when Kind == KindDecl.
    clang::Decl const* Decl;

    // The type when Kind == KindType.
    DumpType Type;

    // The dump status for this node.
    DumpNode const* DN;

    friend bool operator<(QueueEntry const& l, QueueEntry const& r)
    {
      return l.DN->Index < r.DN->Index;
    }
  };

  struct CommentEntry
  {
    unsigned int Index;
    clang::RawComment const* Comment;
    DumpNode const* Attached;
  };

  class PrinterHelper : public clang::PrinterHelper
  {
    ASTVisitor& Visitor;

  public:
    PrinterHelper(ASTVisitor& v)
      : Visitor(v)
    {
    }
    bool handledStmt(clang::Stmt* s, llvm::raw_ostream& os) override
    {
      return this->Visitor.PrintHelpStmt(s, os);
    }
  };

  /** Get the dump status node for a Clang declaration.  */
  DumpNode* GetDumpNode(clang::Decl const* d) { return &this->DeclNodes[d]; }

  /** Get the dump status node for a Clang type.  */
  DumpNode* GetDumpNode(DumpType t) { return &this->TypeNodes[t]; }

  /** Get the dump status node for a qualified DumpId.  */
  DumpNode* GetDumpNode(DumpId id)
  {
    assert(id.Qual);
    return &this->QualNodes[id];
  }

  /** Allocate a dump node for a Clang declaration.  */
  DumpId AddDeclDumpNode(clang::Decl const* d, bool complete,
                         bool forType = false);
  DumpId AddDeclDumpNodeForType(clang::Decl const* d, bool complete,
                                DumpQual dq);

  /** Allocate a dump node for a Clang type.  */
  DumpId AddTypeDumpNode(DumpType dt, bool complete, DumpQual dq = DumpQual());

  /** Allocate a dump node for a qualified DumpId.  */
  DumpId AddQualDumpNode(DumpId id);

  /** Helper common to AddDeclDumpNode and AddTypeDumpNode.  */
  template <typename K>
  DumpId AddDumpNodeImpl(K k, bool complete);

#if LLVM_VERSION_MAJOR >= 22
  /** Remove type info that we do not care about to avoid duplication.  */
  clang::QualType ConsolidateType(clang::QualType t);
  clang::Type const* ConsolidateTypeImpl(clang::Type const* t);
#endif

  /** Allocate a dump node for a source file entry.  */
  unsigned int AddDumpFile(cx::FileEntryRef f);
#if LLVM_VERSION_MAJOR < 12
  unsigned int AddDumpFile(clang::FileEntry const& f)
  {
    return this->AddDumpFile(&f);
  }
#endif

  /** Add class template specializations and instantiations for output.  */
  void AddClassTemplateDecl(clang::ClassTemplateDecl const* d,
                            std::set<DumpId>* emitted = 0);

  /** Add function template specializations and instantiations for output.  */
  void AddFunctionTemplateDecl(clang::FunctionTemplateDecl const* d,
                               std::set<DumpId>* emitted = 0);

  /** Add declaration context members for output.  */
  void AddDeclContextMembers(clang::DeclContext const* dc,
                             std::set<DumpId>& emitted);

  /** Add a starting declaration for output.  */
  void AddStartDecl(clang::Decl const* d);

  /** Queue leftover nodes that do not need complete output.  */
  void QueueIncompleteDumpNodes();

  /** Traverse AST nodes until the queue is empty.  */
  void ProcessQueue();
  void ProcessCommentQueue();
  void ProcessFileQueue();

  /** Output start tags on top of xml file. */
  void OutputStartXMLTags();

  /** Output end tags. */
  void OutputEndXMLTags();

  /** Dispatch output of a declaration.  */
  void OutputDecl(clang::Decl const* d, DumpNode const* dn);

  /** Dispatch output of a qualified or unqualified type.  */
  void OutputType(DumpType dt, DumpNode const* dn);

  /** Output a qualified type.  */
  void OutputCvQualifiedType(DumpNode const* dn);

  /** Get the XML IDREF for the element defining the given
      declaration context (namespace, class, etc.).  */
  DumpId GetContextIdRef(clang::DeclContext const* dc);

  /** Return the unqualified name of the declaration context
      (class, struct, union) of the given method.  */
  std::string GetContextName(clang::CXXMethodDecl const* d);

  /** Print the XML IDREF value referencing the given type.
      If the type has top-level cv-qualifiers, they are
      appended to the numeric id as single characters (c=const,
      v=volatile, r=restrict) to reference the XML ID of
      a CvQualifiedType element describing the qualifiers
      and referencing the unqualified type.  */
  void PrintTypeIdRef(clang::QualType t, bool complete);

  /** Print an id="_<n>" XML unique ID attribute.  */
  void PrintIdAttribute(DumpNode const* dn);

  /** Print a name="..." attribute.  */
  void PrintNameAttribute(std::string const& name);
  void PrintNameAttribute(llvm::StringRef name);

  /** Print a mangled="..." attribute.  */
  void PrintMangledAttribute(clang::NamedDecl const* d);

  /** Print an offset="..." attribute. */
  void PrintOffsetAttribute(unsigned int const& offset);

  /** Print size="..." and align="..." attributes. */
  void PrintABIAttributes(clang::TypeInfo const& t);
  void PrintABIAttributes(clang::TypeDecl const* d);

  /** Print init="..." attribute. */
  void PrintInitAttribute(clang::Expr const* init);

  /** Print a basetype="..." attribute with the XML IDREF for
      the given type.  Also queues the given type for later output.  */
  void PrintBaseTypeAttribute(clang::Type const* c, bool complete);

  /** Print a type="..." attribute with the XML IDREF for
      the given (possibly cv-qualified) type.  Also queues
      the given type for later output.  */
  void PrintTypeAttribute(clang::QualType t, bool complete);

  /** Print a returns="..." attribute with the XML IDREF for
      the given (possibly cv-qualified) type.  Also queue
      the given type for later output.  */
  void PrintReturnsAttribute(clang::QualType t, bool complete);

  /** Print the XML attributes location="fid:line" file="fid" line="line"
      for the given decl.  */
  void PrintLocationAttribute(clang::Decl const* d);

  /** Print a members="..." attribute listing the XML IDREFs for
      members of the given declaration context.  Also queues the
      context members for later output.  */
  void PrintMembersAttribute(clang::DeclContext const* dc);
  void PrintMembersAttribute(std::set<DumpId> const& emitted);

  /** Print a bases="..." attribute listing the XML IDREFs for
      bases of the given class type.  Also queues the base classes
      for later output.  */
  void PrintBasesAttribute(clang::CXXRecordDecl const* dx);

  /** Print an attributes="..." attribute listing the given attributes.  */
  void PrintAttributesAttribute(std::vector<std::string> const& attrs);

  /** Print an attributes="..." attribute listing the given
      declaration's attributes.  */
  void PrintAttributesAttribute(clang::Decl const* d);

  /** Get the attributes of the given function type.  */
  void GetFunctionTypeAttributes(clang::FunctionProtoType const* t,
                                 std::vector<std::string>& attrs);

  /** Get the attributes of the given declaration.  */
  void GetDeclAttributes(clang::Decl const* d,
                         std::vector<std::string>& attrs);

  /** Print a throw="..." attribute listing the XML IDREFs for
      the types that the given function prototype declares in
      the throw() specification.  */
  void PrintThrowsAttribute(clang::FunctionProtoType const* fpt,
                            bool complete);

  /** Print a befriending="..." attribute listing the XML IDREFs for
      friends of the given class.  Also queues the friends for later
      output.  */
  void PrintBefriendingAttribute(clang::CXXRecordDecl const* dx);

  /** Print a comment="..." attribute showing the declaration's comment.  */
  void PrintCommentAttribute(clang::Decl const* d, DumpNode const* dn);

  /** Flags used by function output methods to pass information
      to the OutputFunctionHelper method.  */
  enum FunctionHelperFlags
  {
    FH_Returns = (1 << 0),
    FH_Static = (1 << 1),
    FH_Explicit = (1 << 2),
    FH_Const = (1 << 3),
    FH_Virtual = (1 << 4),
    FH_Pure = (1 << 5),
    FH__Last
  };

  /** Output a function element using the name and flags given by
      the caller.  This encompasses functionality common to all the
      function declaration output methods.  */
  void OutputFunctionHelper(
    clang::FunctionDecl const* d, DumpNode const* dn, char const* tag,
    unsigned int flags,
    cx::optional<std::string> const& name = cx::optional<std::string>());

  /** Output a function type element using the tag given by the caller.
      This encompasses functionality common to all the function type
      output methods.  */
  void OutputFunctionTypeHelper(clang::FunctionProtoType const* t,
                                DumpNode const* dn, char const* tag,
                                clang::Type const* c);

  /** Output an <Argument/> element inside a function element.  */
  void OutputFunctionArgument(clang::ParmVarDecl const* a, bool complete,
                              clang::Expr const* def);

  /** Print some statements (expressions) in a custom form.  */
  bool PrintHelpStmt(clang::Stmt const* s, llvm::raw_ostream& os);

  /** Print an access="..." attribute.  */
  void PrintAccessAttribute(clang::AccessSpecifier as);

  /** Print a context="..." attribute with the XML IDREF for
      the containing declaration context (namespace, class, etc.).
      Also prints access="..." attribute for class members to
      indicate public, protected, or private membership.  */
  void PrintContextAttribute(clang::Decl const* d,
                             clang::AccessSpecifier alt = clang::AS_none);

  bool HaveFloat128Type() const;
  void PrintCastXMLTypedef(clang::TypedefDecl const* d, DumpNode const* dn);
  bool IsCastXMLTypedefType(clang::QualType t) const;
  bool IsCastXMLTypedefDecl(clang::TypedefDecl const* td) const;

#if LLVM_VERSION_MAJOR >= 22
  bool IsElaboratedType(clang::TagType const* t) const
  {
    return (t->getKeyword() != clang::ElaboratedTypeKeyword::None ||
            t->getQualifier());
  }
  bool IsElaboratedType(clang::TemplateSpecializationType const* t) const
  {
    return t->getKeyword() != clang::ElaboratedTypeKeyword::None;
  }
  bool IsElaboratedType(clang::TypedefType const* t) const
  {
    return static_cast<bool>(t->getQualifier());
  }
  clang::QualType GetElaboratedTypeNamed(clang::TagType const* t) const
  {
    return t->getCanonicalTypeUnqualified();
  }
  clang::QualType GetElaboratedTypeNamed(
    clang::TemplateSpecializationType const* t) const
  {
    return t->getCanonicalTypeUnqualified();
  }
  clang::QualType GetElaboratedTypeNamed(clang::TypedefType const* t) const
  {
    return this->CTX.getTypedefType(t->getKeyword(), std::nullopt,
                                    t->getDecl(), t->desugar());
  }
  clang::ElaboratedTypeKeyword GetElaboratedTypeKeyword(
    clang::TagType const* t) const
  {
    return t->getKeyword();
  }
  clang::ElaboratedTypeKeyword GetElaboratedTypeKeyword(
    clang::TemplateSpecializationType const* t) const
  {
    return t->getKeyword();
  }
  clang::ElaboratedTypeKeyword GetElaboratedTypeKeyword(
    clang::TypedefType const*) const
  {
    return clang::ElaboratedTypeKeyword::None;
  }
  cx::NestedNameSpecifier GetElaboratedTypeQualifier(
    clang::TagType const* t) const
  {
    return t->getQualifier();
  }
  cx::NestedNameSpecifier GetElaboratedTypeQualifier(
    clang::TemplateSpecializationType const* t) const
  {
    return std::nullopt;
  }
  cx::NestedNameSpecifier GetElaboratedTypeQualifier(
    clang::TypedefType const* t) const
  {
    return t->getQualifier();
  }
#else
  bool IsElaboratedType(clang::TagType const*) const { return false; }
  bool IsElaboratedType(clang::TemplateSpecializationType const*) const
  {
    return false;
  }
  bool IsElaboratedType(clang::TypedefType const*) const { return false; }
  bool IsElaboratedType(clang::ElaboratedType const* t) const
  {
    return (t->getKeyword() != cx_ElaboratedTypeKeyword(None) ||
            t->getQualifier());
  }
  clang::QualType GetElaboratedTypeNamed(clang::ElaboratedType const* t) const
  {
    return t->getNamedType();
  }
  clang::ElaboratedTypeKeyword GetElaboratedTypeKeyword(
    clang::ElaboratedType const* t) const
  {
    return t->getKeyword();
  }
  cx::NestedNameSpecifier GetElaboratedTypeQualifier(
    clang::ElaboratedType const* t) const
  {
    return t->getQualifier();
  }
#endif

  // Decl node output methods.
  void OutputTranslationUnitDecl(clang::TranslationUnitDecl const* d,
                                 DumpNode const* dn);
  void OutputNamespaceDecl(clang::NamespaceDecl const* d, DumpNode const* dn);
  void OutputRecordDecl(clang::RecordDecl const* d, DumpNode const* dn);
  void OutputCXXRecordDecl(clang::CXXRecordDecl const* d, DumpNode const* dn);
  void OutputClassTemplateSpecializationDecl(
    clang::ClassTemplateSpecializationDecl const* d, DumpNode const* dn);
  void OutputTypedefDecl(clang::TypedefDecl const* d, DumpNode const* dn);
  void OutputTypeAliasDecl(clang::TypeAliasDecl const* d, DumpNode const* dn);
  void OutputEnumDecl(clang::EnumDecl const* d, DumpNode const* dn);
  void OutputFieldDecl(clang::FieldDecl const* d, DumpNode const* dn);
  void OutputVarDecl(clang::VarDecl const* d, DumpNode const* dn);

  void OutputFunctionDecl(clang::FunctionDecl const* d, DumpNode const* dn);
  void OutputCXXMethodDecl(clang::CXXMethodDecl const* d, DumpNode const* dn);
  void OutputCXXConversionDecl(clang::CXXConversionDecl const* d,
                               DumpNode const* dn);
  void OutputCXXConstructorDecl(clang::CXXConstructorDecl const* d,
                                DumpNode const* dn);
  void OutputCXXDestructorDecl(clang::CXXDestructorDecl const* d,
                               DumpNode const* dn);

  // Type node output methods.
  void OutputAtomicType(clang::AtomicType const* t, DumpNode const* dn);
  void OutputAutoType(clang::AutoType const* t, DumpNode const* dn);
  void OutputBuiltinType(clang::BuiltinType const* t, DumpNode const* dn);
  void OutputConstantArrayType(clang::ConstantArrayType const* t,
                               DumpNode const* dn);
  void OutputIncompleteArrayType(clang::IncompleteArrayType const* t,
                                 DumpNode const* dn);
  void OutputFunctionProtoType(clang::FunctionProtoType const* t,
                               DumpNode const* dn);
  void OutputLValueReferenceType(clang::LValueReferenceType const* t,
                                 DumpNode const* dn);
  void OutputRValueReferenceType(clang::RValueReferenceType const* t,
                                 DumpNode const* dn);
  void OutputMemberPointerType(clang::MemberPointerType const* t,
                               DumpNode const* dn);
  void OutputMethodType(clang::FunctionProtoType const* t,
                        clang::Type const* c, DumpNode const* dn);
  void OutputOffsetType(clang::QualType t, clang::Type const* c,
                        DumpNode const* dn);
  void OutputPointerType(clang::PointerType const* t, DumpNode const* dn);

#if LLVM_VERSION_MAJOR >= 22
  void OutputEnumType(clang::EnumType const* t, DumpNode const* dn)
  {
    this->OutputElaboratedTypeImpl(t, dn);
  }
  void OutputRecordType(clang::RecordType const* t, DumpNode const* dn)
  {
    this->OutputElaboratedTypeImpl(t, dn);
  }
  void OutputTypedefType(clang::TypedefType const* t, DumpNode const* dn)
  {
    this->OutputElaboratedTypeImpl(t, dn);
  }
  void OutputTemplateSpecializationType(
    clang::TemplateSpecializationType const* t, DumpNode const* dn)
  {
    this->OutputElaboratedTypeImpl(t, dn);
  }
#else
  void OutputElaboratedType(clang::ElaboratedType const* t, DumpNode const* dn)
  {
    this->OutputElaboratedTypeImpl(t, dn);
  }
#endif
  template <typename T>
  void OutputElaboratedTypeImpl(T const* t, DumpNode const* dn);

  /** Queue declarations matching given qualified name in given context.  */
  void LookupStart(clang::DeclContext const* dc, std::string const& name);

private:
  // List of starting declaration names.
  Options const& Opts;

  // Total number of nodes to be dumped.
  unsigned int NodeCount;

  // Total number of comments to be referenced.
  unsigned int CommentCount = 0;

  // Total number of source files to be referenced.
  unsigned int FileCount;

  // Whether we need a File element for compiler builtins.
  bool FileBuiltin;

  // Whether we are in the complete or incomplete output step.
  bool RequireComplete;

  // Mangling context for target ABI.
  std::unique_ptr<clang::MangleContext> MangleContext;

  // Control declaration and type printing.
  clang::PrintingPolicy PrintingPolicy;

  // Map from clang AST declaration node to our dump status node.
  typedef std::map<clang::Decl const*, DumpNode> DeclNodesMap;
  DeclNodesMap DeclNodes;

#if LLVM_VERSION_MAJOR >= 22
  // Memoize ConsolidateType results.
  typedef std::map<clang::Type const*, clang::Type const*> ConsolidatedTypeMap;
  ConsolidatedTypeMap ConsolidatedType;
#endif

  // Map from clang AST type node to our dump status node.
  typedef std::map<DumpType, DumpNode> TypeNodesMap;
  TypeNodesMap TypeNodes;

  // Map from qualified DumpId to our dump status node.
  typedef std::map<DumpId, DumpNode> QualNodesMap;
  QualNodesMap QualNodes;

  // Map from clang file entry to our source file index.
  typedef std::map<cx::FileEntryRef, unsigned int> FileNodesMap;
  FileNodesMap FileNodes;

  // Node traversal queue.
  std::set<QueueEntry> Queue;

  // Comment traversal queue.
  std::queue<CommentEntry> CommentQueue;

  // File traversal queue.
  std::queue<cx::FileEntryRef> FileQueue;

public:
  ASTVisitor(clang::CompilerInstance& ci, clang::ASTContext& ctx,
             llvm::raw_ostream& os, Options const& opts)
    : ASTVisitorBase(ci, ctx, os)
    , Opts(opts)
    , NodeCount(0)
    , FileCount(0)
    , FileBuiltin(false)
    , RequireComplete(true)
    , MangleContext(ctx.createMangleContext())
    , PrintingPolicy(ctx.getPrintingPolicy())
  {
    this->PrintingPolicy.SuppressUnwrittenScope = true;
  }

  /** Visit declarations in the given translation unit.
      This is the main entry point.  */
  void HandleTranslationUnit(clang::TranslationUnitDecl const* tu);
};

ASTVisitor::DumpId ASTVisitor::AddDeclDumpNode(clang::Decl const* d,
                                               bool complete, bool forType)
{
  // Select the definition or canonical declaration.
  d = d->getCanonicalDecl();
  if (clang::RecordDecl const* rd = clang::dyn_cast<clang::RecordDecl>(d)) {
    if (clang::RecordDecl const* rdd = rd->getDefinition()) {
      d = rdd;
    }
  }

  // Replace some decls with those they reference.
  switch (d->getKind()) {
    case clang::Decl::UsingShadow:
      return this->AddDeclDumpNode(
        static_cast<clang::UsingShadowDecl const*>(d)->getTargetDecl(),
        complete, forType);
    case clang::Decl::LinkageSpec: {
      clang::DeclContext const* dc =
        static_cast<clang::LinkageSpecDecl const*>(d)->getDeclContext();
      return this->AddDeclDumpNode(clang::Decl::castFromDeclContext(dc),
                                   complete, forType);
    } break;
    default:
      break;
  }

  // Skip invalid declarations that are not needed for a type element.
  if (d->isInvalidDecl() && !forType) {
    return DumpId();
  }

  // Skip declarations our output formats do not support.
  if (clang::FunctionDecl const* fd =
        clang::dyn_cast<clang::FunctionDecl>(d)) {
    if (fd->isDeleted()) {
      return DumpId();
    }

    if (fd->getLiteralIdentifier()) {
      return DumpId();
    }

    if (this->Opts.GccXml) {
      if (clang::FunctionProtoType const* fpt =
            fd->getType()->getAs<clang::FunctionProtoType>()) {
        if (fpt->getReturnType()->isRValueReferenceType()) {
          return DumpId();
        }
        for (clang::FunctionProtoType::param_type_iterator
               i = fpt->param_type_begin(),
               e = fpt->param_type_end();
             i != e; ++i) {
          if ((*i)->isRValueReferenceType()) {
            return DumpId();
          }
        }
      }
    }
  }

  if (clang::dyn_cast<clang::TypeAliasTemplateDecl>(d)) {
    return DumpId();
  }

  if (this->Opts.GccXml) {
    if (clang::TypedefDecl const* td =
          clang::dyn_cast<clang::TypedefDecl>(d)) {
      if (td->getUnderlyingType()->isRValueReferenceType()) {
        return DumpId();
      }
    }
  }

  return this->AddDumpNodeImpl(d, complete);
}

ASTVisitor::DumpId ASTVisitor::AddDeclDumpNodeForType(clang::Decl const* d,
                                                      bool complete,
                                                      DumpQual dq)
{
  // Get the id for the canonical decl.
  DumpId id = this->AddDeclDumpNode(d, complete, true);

  // If any qualifiers were collected through layers of desugaring
  // then get the id of the qualified type referencing this decl.
  if (id && dq) {
    id = this->AddQualDumpNode(DumpId(id.Id, dq));
  }

  return id;
}

ASTVisitor::DumpId ASTVisitor::AddTypeDumpNode(DumpType dt, bool complete,
                                               DumpQual dq)
{
#if LLVM_VERSION_MAJOR >= 22
  dt.Type = this->ConsolidateType(dt.Type);
#endif
  clang::QualType t = dt.Type;
  clang::Type const* c = dt.Class;

  // Extract local qualifiers and recurse with locally unqualified type.
  if (t.hasLocalQualifiers()) {
    dq.IsConst = dq.IsConst || t.isLocalConstQualified();
    dq.IsVolatile = dq.IsVolatile || t.isLocalVolatileQualified();
    dq.IsRestrict = dq.IsRestrict || t.isLocalRestrictQualified();
    return this->AddTypeDumpNode(DumpType(t.getLocalUnqualifiedType(), c),
                                 complete, dq);
  }

  // Replace some types with their decls.
  switch (t->getTypeClass()) {
    case clang::Type::Adjusted: {
      auto const* at = static_cast<clang::AdjustedType const*>(t.getTypePtr());
      return this->AddTypeDumpNode(DumpType(at->getAdjustedType(), c),
                                   complete, dq);
    } break;
    case clang::Type::Attributed: {
      auto const* at =
        static_cast<clang::AttributedType const*>(t.getTypePtr());
      return this->AddTypeDumpNode(DumpType(at->getEquivalentType(), c),
                                   complete, dq);
    } break;
    case clang::Type::Auto: {
      auto const* at = static_cast<clang::AutoType const*>(t.getTypePtr());
      if (at->isSugared()) {
        return this->AddTypeDumpNode(DumpType(at->desugar(), c), complete, dq);
      }
    } break;
    case clang::Type::Decayed: {
      auto const* dt = static_cast<clang::DecayedType const*>(t.getTypePtr());
      return this->AddTypeDumpNode(DumpType(dt->getDecayedType(), c), complete,
                                   dq);
    } break;
    case clang::Type::Decltype: {
      if (this->Opts.CastXml) {
        auto const* dtt =
          static_cast<clang::DecltypeType const*>(t.getTypePtr());
        return this->AddTypeDumpNode(DumpType(dtt->getUnderlyingType(), c),
                                     complete, dq);
      }
    } break;
#if LLVM_VERSION_MAJOR < 22
    case clang::Type::Elaborated: {
      auto const* et =
        static_cast<clang::ElaboratedType const*>(t.getTypePtr());
      if (this->Opts.GccXml || !this->IsElaboratedType(et)) {
        // The gccxml format does not include ElaboratedType elements,
        // so replace this one with the underlying type.  Note that this
        // can cause duplicate PointerType and ReferenceType elements
        // to appear in the output: each copy corresponds to a different
        // ElaboratedType, but the gccxml format cannot express this.
        return this->AddTypeDumpNode(DumpType(et->getNamedType(), c), complete,
                                     dq);
      }
    } break;
#endif
    case clang::Type::Enum: {
      auto const* et = static_cast<clang::EnumType const*>(t.getTypePtr());
      if (this->Opts.GccXml || !this->IsElaboratedType(et)) {
        return this->AddDeclDumpNodeForType(et->getDecl(), complete, dq);
      }
    } break;
    case clang::Type::Paren: {
      auto const* pt = static_cast<clang::ParenType const*>(t.getTypePtr());
      return this->AddTypeDumpNode(DumpType(pt->getInnerType(), c), complete,
                                   dq);
    } break;
#if LLVM_VERSION_MAJOR >= 22
    case clang::Type::PredefinedSugar: {
      clang::PredefinedSugarType const* st =
        static_cast<clang::PredefinedSugarType const*>(t.getTypePtr());
      return this->AddTypeDumpNode(DumpType(st->desugar(), c), complete, dq);
    } break;
#endif
    case clang::Type::Record: {
      auto const* rt = static_cast<clang::RecordType const*>(t.getTypePtr());
      if (this->Opts.GccXml || !this->IsElaboratedType(rt)) {
        return this->AddDeclDumpNodeForType(rt->getDecl(), complete, dq);
      }
    } break;
    case clang::Type::SubstTemplateTypeParm: {
      auto const* st =
        static_cast<clang::SubstTemplateTypeParmType const*>(t.getTypePtr());
      return this->AddTypeDumpNode(DumpType(st->getReplacementType(), c),
                                   complete, dq);
    } break;
    case clang::Type::TemplateSpecialization: {
      auto const* tst =
        static_cast<clang::TemplateSpecializationType const*>(t.getTypePtr());
      if (this->Opts.GccXml || !this->IsElaboratedType(tst)) {
        if (tst->isSugared()) {
          return this->AddTypeDumpNode(DumpType(tst->desugar(), c), complete,
                                       dq);
        }
      }
    } break;
    case clang::Type::Typedef: {
      auto const* tdt = static_cast<clang::TypedefType const*>(t.getTypePtr());
      if (!tdt->isInstantiationDependentType() && tdt->isSugared()) {
        // Make sure all containing contexts are not templates.
        clang::Decl const* d = tdt->getDecl();
        while (clang::DeclContext const* tdc = d->getDeclContext()) {
          if (clang::CXXRecordDecl const* tdx =
                clang::dyn_cast<clang::CXXRecordDecl>(tdc)) {
            d = tdx;
            if (tdx->getDescribedClassTemplate() ||
                clang::isa<clang::ClassTemplatePartialSpecializationDecl>(
                  tdx)) {
              // This TypedefType refers to a non-dependent
              // TypedefDecl member of a class template.  Since gccxml
              // format does not include uninstantiated templates we
              // must use the desugared type so that we do not end up
              // referencing a class template as context.
              return this->AddTypeDumpNode(tdt->desugar(), complete, dq);
            }
          } else {
            break;
          }
        }
      }
      if (this->Opts.GccXml || !this->IsElaboratedType(tdt)) {
        return this->AddDeclDumpNodeForType(tdt->getDecl(), complete, dq);
      }
    } break;
#if LLVM_VERSION_MAJOR >= 14
    case clang::Type::Using: {
      auto const* ut = static_cast<clang::UsingType const*>(t.getTypePtr());
      return this->AddTypeDumpNode(DumpType(ut->desugar(), c), complete, dq);
    } break;
#endif
    default:
      break;
  }

  // Get the id for the fully desugared, unqualified type.
  DumpId id = this->AddDumpNodeImpl(dt, complete);

  // If any qualifiers were collected through layers of desugaring
  // then get the id of the qualified type.
  if (id && dq) {
    id = this->AddQualDumpNode(DumpId(id.Id, dq));
  }

  return id;
}

#if LLVM_VERSION_MAJOR >= 22
clang::QualType ASTVisitor::ConsolidateType(clang::QualType t)
{
  clang::Type const* oldTy = t.getTypePtr();
  clang::Type const*& newTy = this->ConsolidatedType[oldTy];
  if (!newTy) {
    newTy = this->ConsolidateTypeImpl(oldTy);
    assert(newTy != nullptr);
  }
  return clang::QualType(newTy, t.getLocalFastQualifiers());
}

clang::Type const* ASTVisitor::ConsolidateTypeImpl(clang::Type const* t)
{
  switch (t->getTypeClass()) {
    case clang::Type::LValueReference: {
      auto const* rt = static_cast<clang::LValueReferenceType const*>(t);
      clang::QualType oldPointeeType = rt->getPointeeType();
      clang::QualType newPointeeType = this->ConsolidateType(oldPointeeType);
      if (newPointeeType != oldPointeeType) {
        return this->CTX.getLValueReferenceType(newPointeeType).getTypePtr();
      }
    } break;
    case clang::Type::Pointer: {
      auto const* pt = static_cast<clang::PointerType const*>(t);
      clang::QualType oldPointeeType = pt->getPointeeType();
      clang::QualType newPointeeType = this->ConsolidateType(oldPointeeType);
      if (newPointeeType != oldPointeeType) {
        return this->CTX.getPointerType(newPointeeType).getTypePtr();
      }
    } break;
    case clang::Type::RValueReference: {
      auto const* rt = static_cast<clang::RValueReferenceType const*>(t);
      clang::QualType oldPointeeType = rt->getPointeeType();
      clang::QualType newPointeeType = this->ConsolidateType(oldPointeeType);
      if (newPointeeType != oldPointeeType) {
        return this->CTX.getRValueReferenceType(newPointeeType).getTypePtr();
      }
    } break;
    case clang::Type::Record: {
      auto const* rt = static_cast<clang::RecordType const*>(t);
      // Clang allocates separate RecordType instances for a record decl
      // and its injected class name.  Replace the latter with the former.
      if (rt->isInjected()) {
        if (auto const* rd =
              clang::dyn_cast<clang::CXXRecordDecl>(rt->getDecl())) {
          if (rd->isInjectedClassName()) {
            rd =
              static_cast<clang::CXXRecordDecl const*>(rd->getDeclContext());
          }
          return this->CTX
            .getTagType(rt->getKeyword(), rt->getQualifier(), rd, false)
            .getTypePtr();
        }
      }
    } break;
    default:
      break;
  }
  return t;
}
#endif

ASTVisitor::DumpId ASTVisitor::AddQualDumpNode(DumpId id)
{
  DumpNode* dn = this->GetDumpNode(id);
  if (!dn->Index) {
    dn->Index = id;
    // Always treat CvQualifiedType nodes as complete.
    dn->Complete = true;
    this->Queue.insert(QueueEntry(dn));
  }
  return dn->Index;
}

template <typename K>
ASTVisitor::DumpId ASTVisitor::AddDumpNodeImpl(K k, bool complete)
{
  // Update an existing node or add one.
  DumpNode* dn = this->GetDumpNode(k);
  if (dn->Index) {
    // Node was already encountered.  See if it is now complete.
    if (complete && !dn->Complete) {
      // Node is now complete, but wasn't before.  Queue it.
      dn->Complete = true;
      this->Queue.insert(QueueEntry(k, dn));
    }
  } else {
    // This is a new node.  Assign it an index.
    dn->Index.Id = ++this->NodeCount;
    dn->Complete = complete;
    if (complete || !this->RequireComplete) {
      // Node is complete.  Queue it.
      this->Queue.insert(QueueEntry(k, dn));
    }
  }
  // Return node's index.
  return dn->Index;
}

unsigned int ASTVisitor::AddDumpFile(cx::FileEntryRef f)
{
  unsigned int& index = this->FileNodes[f];
  if (index == 0) {
    index = ++this->FileCount;
    this->FileQueue.push(f);
  }
  return index;
}

void ASTVisitor::AddClassTemplateDecl(clang::ClassTemplateDecl const* d,
                                      std::set<DumpId>* emitted)
{
  // Queue all the instantiations of this class template.
  for (clang::ClassTemplateDecl::spec_iterator i = d->spec_begin(),
                                               e = d->spec_end();
       i != e; ++i) {
    clang::CXXRecordDecl const* rd = *i;
    DumpId id = this->AddDeclDumpNode(rd, true);
    if (id && emitted) {
      emitted->insert(id);
    }
  }
}

void ASTVisitor::AddFunctionTemplateDecl(clang::FunctionTemplateDecl const* d,
                                         std::set<DumpId>* emitted)
{
  // Queue all the instantiations of this function template.
  for (clang::FunctionTemplateDecl::spec_iterator i = d->spec_begin(),
                                                  e = d->spec_end();
       i != e; ++i) {
    clang::FunctionDecl const* fd = *i;
    DumpId id = this->AddDeclDumpNode(fd, true);
    if (id && emitted) {
      emitted->insert(id);
    }
  }
}

void ASTVisitor::AddDeclContextMembers(clang::DeclContext const* dc,
                                       std::set<DumpId>& emitted)
{
  bool const isTranslationUnit = clang::isa<clang::TranslationUnitDecl>(dc);

  for (clang::DeclContext::decl_iterator i = dc->decls_begin(),
                                         e = dc->decls_end();
       i != e; ++i) {
    clang::Decl const* d = *i;

    // Skip declarations that are not really members of this context.
    if (d->getDeclContext() != dc) {
      continue;
    }

    // Skip declarations that we use internally as builtins.
    if (isTranslationUnit) {
      if (clang::NamedDecl const* nd = clang::dyn_cast<clang::NamedDecl>(d)) {
        if (clang::IdentifierInfo const* ii = nd->getIdentifier()) {
          if (ii->getName().find("__castxml") != std::string::npos) {
            continue;
          }
        }
      }
    }

    // Ignore certain members.
    switch (d->getKind()) {
      case clang::Decl::CXXRecord: {
        clang::CXXRecordDecl const* rd =
          static_cast<clang::CXXRecordDecl const*>(d);
        if (rd->isInjectedClassName()) {
          continue;
        }
      } break;
      case clang::Decl::AccessSpec: {
        continue;
      } break;
      case clang::Decl::ClassTemplate: {
        this->AddClassTemplateDecl(
          static_cast<clang::ClassTemplateDecl const*>(d), &emitted);
        continue;
      } break;
      case clang::Decl::ClassTemplatePartialSpecialization: {
        continue;
      } break;
      case clang::Decl::Empty: {
        continue;
      } break;
      case clang::Decl::Friend: {
        continue;
      } break;
      case clang::Decl::FunctionTemplate: {
        this->AddFunctionTemplateDecl(
          static_cast<clang::FunctionTemplateDecl const*>(d), &emitted);
        continue;
      } break;
      case clang::Decl::LinkageSpec: {
        this->AddDeclContextMembers(
          static_cast<clang::LinkageSpecDecl const*>(d), emitted);
        continue;
      } break;
      case clang::Decl::Namespace: {
        clang::NamespaceDecl const* nd =
          static_cast<clang::NamespaceDecl const*>(d);
        if (nd->isInline()) {
          this->AddDeclContextMembers(nd, emitted);
          continue;
        }
      } break;
      case clang::Decl::Using: {
        continue;
      } break;
      case clang::Decl::UsingDirective: {
        continue;
      } break;
      default:
        break;
    }

    // Queue this decl and print its id.
    if (DumpId id = this->AddDeclDumpNode(d, true)) {
      emitted.insert(id);
    }
  }
}

void ASTVisitor::AddStartDecl(clang::Decl const* d)
{
  switch (d->getKind()) {
    case clang::Decl::ClassTemplate:
      this->AddClassTemplateDecl(
        static_cast<clang::ClassTemplateDecl const*>(d));
      break;
    case clang::Decl::FunctionTemplate:
      this->AddFunctionTemplateDecl(
        static_cast<clang::FunctionTemplateDecl const*>(d));
      break;
    case clang::Decl::Namespace: {
      if (!static_cast<clang::NamespaceDecl const*>(d)->isInline()) {
        this->AddDeclDumpNode(d, true);
      }
    } break;
    case clang::Decl::Using: {
      clang::UsingDecl const* ud = static_cast<clang::UsingDecl const*>(d);
      for (clang::UsingDecl::shadow_iterator i = ud->shadow_begin(),
                                             e = ud->shadow_end();
           i != e; ++i) {
        this->AddDeclDumpNode(*i, true);
      }
    } break;
    default:
      this->AddDeclDumpNode(d, true);
      break;
  }
}

void ASTVisitor::QueueIncompleteDumpNodes()
{
  // Queue declaration nodes that do not need complete output.
  for (DeclNodesMap::const_iterator i = this->DeclNodes.begin(),
                                    e = this->DeclNodes.end();
       i != e; ++i) {
    if (!i->second.Complete) {
      this->Queue.insert(QueueEntry(i->first, &i->second));
    }
  }

  // Queue type nodes that do not need complete output.
  for (TypeNodesMap::const_iterator i = this->TypeNodes.begin(),
                                    e = this->TypeNodes.end();
       i != e; ++i) {
    if (!i->second.Complete) {
      this->Queue.insert(QueueEntry(i->first, &i->second));
    }
  }
}

void ASTVisitor::ProcessQueue()
{
  // Dispatch each entry in the queue based on its node kind.
  while (!this->Queue.empty()) {
    QueueEntry qe = *this->Queue.begin();
    this->Queue.erase(this->Queue.begin());
    switch (qe.Kind) {
      case QueueEntry::KindQual:
        this->OutputCvQualifiedType(qe.DN);
        break;
      case QueueEntry::KindDecl:
        this->OutputDecl(qe.Decl, qe.DN);
        break;
      case QueueEntry::KindType:
        this->OutputType(qe.Type, qe.DN);
        break;
    }
  }
}

void ASTVisitor::ProcessCommentQueue()
{
  while (!this->CommentQueue.empty()) {
    CommentEntry c = this->CommentQueue.front();
    this->CommentQueue.pop();

    this->OS << "  <Comment id=\"c" << c.Index << "\"";
    if (c.Attached) {
      this->OS << " attached=\"_" << c.Attached->Index << "\"";
    }
    clang::SourceLocation bl = c.Comment->getSourceRange().getBegin();
    clang::SourceLocation el = c.Comment->getSourceRange().getEnd();
    if (bl.isValid() && el.isValid()) {
      clang::FullSourceLoc bfl = this->CTX.getFullLoc(bl).getExpansionLoc();
      clang::FullSourceLoc efl = this->CTX.getFullLoc(el).getExpansionLoc();
      clang::SourceManager const& sm = this->CI.getSourceManager();
      clang::FileID bid = bfl.getFileID();
      clang::FileID eid = efl.getFileID();
      cx::OptionalFileEntryRef bf = this->getFileEntryRefForID(bid);
      cx::OptionalFileEntryRef ef = this->getFileEntryRefForID(eid);
      if (bf && bf == ef) {
        unsigned int fi = this->AddDumpFile(*bf);
        unsigned int boff = sm.getFileOffset(bfl);
        unsigned int eoff = sm.getFileOffset(efl);
        /* clang-format off */
        this->OS <<
         " file=\"f" << fi << "\""
         " begin_line=\"" << sm.getLineNumber(bid, boff) << "\""
         " begin_column=\"" << sm.getColumnNumber(bid, boff) << "\""
         " begin_offset=\"" << boff << "\""
         " end_line=\"" << sm.getLineNumber(eid, eoff) << "\""
         " end_column=\"" << sm.getColumnNumber(eid, eoff) << "\""
         " end_offset=\"" << eoff << "\""
         ;
        /* clang-format on */
      }
    }
    this->OS << "/>\n";
  }
}

void ASTVisitor::ProcessFileQueue()
{
  if (this->FileBuiltin) {
    /* clang-format off */
    this->OS <<
      "  <File id=\"f0\" name=\"" << encodeXML("<builtin>") << "\"/>\n"
      ;
    /* clang-format on */
  }
  while (!this->FileQueue.empty()) {
    cx::FileEntryRef f = this->FileQueue.front();
    this->FileQueue.pop();
    /* clang-format off */
    this->OS <<
      "  <File"
      " id=\"f" << this->FileNodes[f] << "\""
      " name=\"" << encodeXML(this->getNameOfFileEntryRef(f)) << "\""
      "/>\n"
      ;
    /* clang-format on */
  }
}

void ASTVisitor::OutputDecl(clang::Decl const* d, DumpNode const* dn)
{
  // Dispatch output of the declaration.
  switch (d->getKind()) {
#define ABSTRACT_DECL(DECL)
#define DECL(CLASS, BASE)                                                     \
  case clang::Decl::CLASS:                                                    \
    this->Output##CLASS##Decl(static_cast<clang::CLASS##Decl const*>(d), dn); \
    break;
#include "clang/AST/DeclNodes.inc"
  }
}

void ASTVisitor::OutputType(DumpType dt, DumpNode const* dn)
{
  clang::QualType t = dt.Type;
  clang::Type const* c = dt.Class;

  if (c) {
    // Output the method type.
    this->OutputMethodType(t->getAs<clang::FunctionProtoType>(), c, dn);
  } else {
    // Dispatch output of the unqualified type.
    switch (t->getTypeClass()) {
#define ABSTRACT_TYPE(CLASS, BASE)
#define TYPE(CLASS, BASE)                                                     \
  case clang::Type::CLASS:                                                    \
    this->Output##CLASS##Type(                                                \
      static_cast<clang::CLASS##Type const*>(t.getTypePtr()), dn);            \
    break;
#if LLVM_VERSION_MAJOR >= 10
#  include "clang/AST/TypeNodes.inc"
#else
#  include "clang/AST/TypeNodes.def"
#endif
    }
  }
}

void ASTVisitor::OutputCvQualifiedType(DumpNode const* dn)
{
  DumpId id = dn->Index;

  // Create a special CvQualifiedType element to hold top-level
  // cv-qualifiers for a real type node.
  this->OS << "  <CvQualifiedType id=\"_" << id << "\"";

  // Refer to the unqualified type.
  this->OS << " type=\"_" << id.Id << "\"";

  // Add the cv-qualification attributes.
  if (id.Qual.IsConst) {
    this->OS << " const=\"1\"";
  }
  if (id.Qual.IsVolatile) {
    this->OS << " volatile=\"1\"";
  }
  if (id.Qual.IsRestrict) {
    this->OS << " restrict=\"1\"";
  }
  this->OS << "/>\n";
}

ASTVisitor::DumpId ASTVisitor::GetContextIdRef(clang::DeclContext const* dc)
{
  while (dc->isInlineNamespace()) {
    dc = dc->getParent();
  }

  if (clang::Decl const* d = clang::dyn_cast<clang::Decl>(dc)) {
    return this->AddDeclDumpNode(d, false);
  } else {
    return DumpId();
  }
}

std::string ASTVisitor::GetContextName(clang::CXXMethodDecl const* d)
{
  clang::DeclContext const* dc = d->getDeclContext();
  if (clang::RecordDecl const* rd = clang::dyn_cast<clang::RecordDecl>(dc)) {
    return rd->getName().str();
  }
  return "";
}

void ASTVisitor::PrintTypeIdRef(clang::QualType t, bool complete)
{
  // Add the type node.
  DumpId id = this->AddTypeDumpNode(t, complete);

  // Print the reference.
  this->OS << "_" << id;
}

void ASTVisitor::PrintIdAttribute(DumpNode const* dn)
{
  this->OS << " id=\"_" << dn->Index << "\"";
}

void ASTVisitor::PrintNameAttribute(std::string const& name)
{
  std::string n = name;
  n = stringReplace(n, "__castxml__float128_s", "__float128");
  n = stringReplace(n, "__castxml_Float32_s", "_Float32");
  n = stringReplace(n, "__castxml_Float32x_s", "_Float32x");
  n = stringReplace(n, "__castxml_Float64_s", "_Float64");
  n = stringReplace(n, "__castxml_Float64x_s", "_Float64x");
  n = stringReplace(n, "__castxml_Float128_s", "_Float128");
  this->OS << " name=\"" << encodeXML(n) << "\"";
}

void ASTVisitor::PrintNameAttribute(llvm::StringRef name)
{
  this->PrintNameAttribute(std::string(name));
}

void ASTVisitor::PrintMangledAttribute(clang::NamedDecl const* d)
{
  // Compute the mangled name.
  std::string s;
  {
    llvm::raw_string_ostream rso(s);
    this->MangleContext->mangleName(d, rso);
  }

  // We cannot mangle some types correctly because Clang does not have
  // them as internal types, so skip mangled attributes involving them.
  if (s.find("__castxml") != std::string::npos) {
    s = "";
  }

  // Strip a leading 1 byte in MS mangling.
  if (!s.empty() && s[0] == '\1') {
    s = s.substr(1);
  }

  this->OS << " mangled=\"" << encodeXML(s) << "\"";
}

void ASTVisitor::PrintOffsetAttribute(unsigned int const& offset)
{
  this->OS << " offset=\"" << offset << "\"";
}

void ASTVisitor::PrintABIAttributes(clang::TypeDecl const* d)
{
  if (clang::TypeDecl const* td = clang::dyn_cast<clang::TypeDecl>(d)) {
    clang::QualType ty = this->CTX.getTypeDeclType(td);
    if (!ty->isIncompleteType()) {
      this->PrintABIAttributes(this->CTX.getTypeInfo(ty));
    }
  }
}

void ASTVisitor::PrintABIAttributes(clang::TypeInfo const& t)
{
  this->OS << " size=\"" << t.Width << "\"";
  this->OS << " align=\"" << t.Align << "\"";
}

void ASTVisitor::PrintInitAttribute(clang::Expr const* init)
{
  if (!init) {
    return;
  }
  this->OS << " init=\"";
  std::string s;
  llvm::raw_string_ostream rso(s);
  PrinterHelper ph(*this);
  init->printPretty(rso, &ph, this->PrintingPolicy);
  this->OS << encodeXML(rso.str());
  this->OS << "\"";
}

void ASTVisitor::PrintBaseTypeAttribute(clang::Type const* c, bool complete)
{
  this->OS << " basetype=\"";
  this->PrintTypeIdRef(clang::QualType(c, 0), complete);
  this->OS << "\"";
}

void ASTVisitor::PrintTypeAttribute(clang::QualType t, bool complete)
{
  this->OS << " type=\"";
  this->PrintTypeIdRef(t, complete);
  this->OS << "\"";
}

void ASTVisitor::PrintReturnsAttribute(clang::QualType t, bool complete)
{
  this->OS << " returns=\"";
  this->PrintTypeIdRef(t, complete);
  this->OS << "\"";
}

void ASTVisitor::PrintLocationAttribute(clang::Decl const* d)
{
  clang::SourceLocation sl = d->getLocation();
  if (sl.isValid()) {
    clang::FullSourceLoc fsl = this->CTX.getFullLoc(sl).getExpansionLoc();
    if (cx::OptionalFileEntryRef f =
          this->getFileEntryRefForID(fsl.getFileID())) {
      unsigned int id = this->AddDumpFile(*f);
      unsigned int line = fsl.getExpansionLineNumber();
      /* clang-format off */
      this->OS <<
        " location=\"f" << id << ":" << line << "\""
        " file=\"f" << id << "\""
        " line=\"" << line << "\"";
      /* clang-format on */
      return;
    }
  }
  if (d->isImplicit()) {
    this->FileBuiltin = true;
    this->OS << " location=\"f0:0\" file=\"f0\" line=\"0\"";
  }
}

bool ASTVisitor::PrintHelpStmt(clang::Stmt const* s, llvm::raw_ostream& os)
{
  switch (s->getStmtClass()) {
    case clang::Stmt::CStyleCastExprClass: {
      // Duplicate clang::StmtPrinter::VisitCStyleCastExpr
      // but with canonical type so we do not print an unqualified name.
      clang::CStyleCastExpr const* e =
        static_cast<clang::CStyleCastExpr const*>(s);
      os << "(";
      e->getTypeAsWritten().getCanonicalType().print(os, this->PrintingPolicy);
      os << ")";
      PrinterHelper ph(*this);
      e->getSubExpr()->printPretty(os, &ph, this->PrintingPolicy);
      return true;
    } break;
    case clang::Stmt::CXXConstCastExprClass:       // fallthrough
    case clang::Stmt::CXXDynamicCastExprClass:     // fallthrough
    case clang::Stmt::CXXReinterpretCastExprClass: // fallthrough
    case clang::Stmt::CXXStaticCastExprClass: {
      // Duplicate clang::StmtPrinter::VisitCXXNamedCastExpr
      // but with canonical type so we do not print an unqualified name.
      clang::CXXNamedCastExpr const* e =
        static_cast<clang::CXXNamedCastExpr const*>(s);
      os << e->getCastName() << '<';
      e->getTypeAsWritten().getCanonicalType().print(os, this->PrintingPolicy);
      os << ">(";
      PrinterHelper ph(*this);
      e->getSubExpr()->printPretty(os, &ph, this->PrintingPolicy);
      os << ")";
      return true;
    } break;
    case clang::Stmt::DeclRefExprClass: {
      // Print the fully qualified name of the referenced declaration.
      clang::DeclRefExpr const* e = static_cast<clang::DeclRefExpr const*>(s);
      if (clang::NamedDecl const* d =
            clang::dyn_cast<clang::NamedDecl>(e->getDecl())) {
        std::string s;
        {
          llvm::raw_string_ostream rso(s);
          d->printQualifiedName(rso, this->PrintingPolicy);
          rso.str();
        }
        if (clang::isa<clang::EnumConstantDecl>(d)) {
          // Clang does not exclude the "::" after an unnamed enum type.
          std::string::size_type pos = s.find("::::");
          if (pos != s.npos) {
            s.erase(pos, 2);
          }
        }
        os << s;
        return true;
      }
    } break;
    default:
      break;
  }
  return false;
}

void ASTVisitor::PrintAccessAttribute(clang::AccessSpecifier as)
{
  switch (as) {
    case clang::AS_private:
      this->OS << " access=\"private\"";
      break;
    case clang::AS_protected:
      this->OS << " access=\"protected\"";
      break;
    case clang::AS_public:
      this->OS << " access=\"public\"";
      break;
    case clang::AS_none:
      break;
  }
}

void ASTVisitor::PrintContextAttribute(clang::Decl const* d,
                                       clang::AccessSpecifier alt)
{
  clang::DeclContext const* dc = d->getDeclContext();
  if (DumpId id = this->GetContextIdRef(dc)) {
    this->OS << " context=\"_" << id << "\"";
    if (dc->isRecord()) {
      clang::AccessSpecifier as = d->getAccess();
      this->PrintAccessAttribute(as != clang::AS_none ? as : alt);
    }
  }
}

void ASTVisitor::PrintMembersAttribute(clang::DeclContext const* dc)
{
  std::set<DumpId> emitted;
  this->AddDeclContextMembers(dc, emitted);
  this->PrintMembersAttribute(emitted);
}

void ASTVisitor::PrintMembersAttribute(std::set<DumpId> const& emitted)
{
  if (!emitted.empty()) {
    this->OS << " members=\"";
    char const* sep = "";
    for (std::set<DumpId>::const_iterator i = emitted.begin(),
                                          e = emitted.end();
         i != e; ++i) {
      this->OS << sep << "_" << *i;
      sep = " ";
    }
    this->OS << "\"";
  }
}

void ASTVisitor::PrintBasesAttribute(clang::CXXRecordDecl const* dx)
{
  this->OS << " bases=\"";
  char const* sep = "";
  for (clang::CXXRecordDecl::base_class_const_iterator i = dx->bases_begin(),
                                                       e = dx->bases_end();
       i != e; ++i) {
    this->OS << sep;
    sep = " ";
    switch (i->getAccessSpecifier()) {
      case clang::AS_private:
        this->OS << "private:";
        break;
      case clang::AS_protected:
        this->OS << "protected:";
        break;
      default:
        break;
    }
    this->PrintTypeIdRef(i->getType().getCanonicalType(), true);
  }
  this->OS << "\"";
}

void ASTVisitor::PrintAttributesAttribute(
  std::vector<std::string> const& attrs)
{
  if (attrs.empty()) {
    return;
  }
  this->OS << " attributes=\"";
  char const* sep = "";
  for (std::string const& a : attrs) {
    this->OS << sep << encodeXML(a);
    sep = " ";
  }
  this->OS << "\"";
}

void ASTVisitor::PrintAttributesAttribute(clang::Decl const* d)
{
  std::vector<std::string> attributes;
  this->GetDeclAttributes(d, attributes);
  this->PrintAttributesAttribute(attributes);
}

void ASTVisitor::GetFunctionTypeAttributes(clang::FunctionProtoType const* t,
                                           std::vector<std::string>& attrs)
{
  switch (t->getExtInfo().getCC()) {
    case clang::CallingConv::CC_C:
      break;
    case clang::CallingConv::CC_X86StdCall:
      attrs.push_back("__stdcall__");
      break;
    case clang::CallingConv::CC_X86FastCall:
      attrs.push_back("__fastcall__");
      break;
    case clang::CallingConv::CC_X86ThisCall:
      attrs.push_back("__thiscall__");
      break;
    default:
      break;
  }
}

void ASTVisitor::GetDeclAttributes(clang::Decl const* d,
                                   std::vector<std::string>& attrs)
{
  std::string annotationXML;
  for (auto const* a : d->specific_attrs<clang::AnnotateAttr>()) {
    attrs.push_back("annotate(" + a->getAnnotation().str() + ")");
    if (!annotationXML.empty()) {
      // Separate annotations by a newline.
      annotationXML += "&#xA;"; // LF
    }
    annotationXML += encodeXML(a->getAnnotation().str());
  }
  if (d->hasAttr<clang::AnnotateAttr>()) {
    if (this->Opts.CastXml) {
      this->OS << " annotation=\"" << annotationXML << "\"";
    }
  }

  if (d->hasAttr<clang::DeprecatedAttr>()) {
    attrs.push_back("deprecated");
    if (this->Opts.CastXml) {
      clang::DeprecatedAttr* depAttr = d->getAttr<clang::DeprecatedAttr>();
      if (!depAttr->getMessage().empty()) {
        std::string depMsgText = std::string(depAttr->getMessage());
        this->OS << " deprecation=\"" << encodeXML(depMsgText) << "\"";
      }
    }
  }

  if (d->hasAttr<clang::DLLExportAttr>()) {
    attrs.push_back("dllexport");
  }

  if (d->hasAttr<clang::DLLImportAttr>()) {
    attrs.push_back("dllimport");
  }

  if (d->hasAttr<clang::FinalAttr>()) {
    attrs.push_back("final");
  }

  if (d->hasAttr<clang::OverrideAttr>()) {
    attrs.push_back("override");
  }
}

void ASTVisitor::PrintThrowsAttribute(clang::FunctionProtoType const* fpt,
                                      bool complete)
{
  if (fpt && fpt->hasDynamicExceptionSpec()) {
    clang::FunctionProtoType::exception_iterator i = fpt->exception_begin();
    clang::FunctionProtoType::exception_iterator e = fpt->exception_end();
    this->OS << " throw=\"";
    char const* sep = "";
    for (; i != e; ++i) {
      this->OS << sep;
      this->PrintTypeIdRef(*i, complete);
      sep = " ";
    }
    this->OS << "\"";
  }
}

void ASTVisitor::PrintBefriendingAttribute(clang::CXXRecordDecl const* dx)
{
  if (dx && dx->hasFriends()) {
    this->OS << " befriending=\"";
    char const* sep = "";
    for (clang::CXXRecordDecl::friend_iterator i = dx->friend_begin(),
                                               e = dx->friend_end();
         i != e; ++i) {
      clang::FriendDecl const* fd = *i;
      if (clang::NamedDecl const* nd = fd->getFriendDecl()) {
        if (nd->isTemplateDecl()) {
          // gccxml output format does not have uninstantiated templates
          continue;
        }

        if (DumpId id = this->AddDeclDumpNode(nd, false)) {
          this->OS << sep << "_" << id;
          sep = " ";
        }
      } else if (clang::TypeSourceInfo const* tsi = fd->getFriendType()) {
        this->OS << sep;
        this->PrintTypeIdRef(tsi->getType(), false);
        sep = " ";
      }
    }
    this->OS << "\"";
  }
}

void ASTVisitor::PrintCommentAttribute(clang::Decl const* d,
                                       DumpNode const* dn)
{
  if (!this->Opts.CastXml) {
    return;
  }
  if (clang::RawComment const* rc = this->CTX.getRawCommentForDeclNoCache(d)) {
    unsigned int index = ++this->CommentCount;
    CommentEntry e = { index, rc, dn };
    this->CommentQueue.push(e);
    this->OS << " comment=\"c" << index << "\"";
  }
}

bool ASTVisitor::HaveFloat128Type() const
{
#if LLVM_VERSION_MAJOR > 3 || LLVM_VERSION_MAJOR == 3 && LLVM_VERSION_MINOR > 8
  return this->CI.getTarget().hasFloat128Type();
#else
  return false;
#endif
}

void ASTVisitor::PrintCastXMLTypedef(clang::TypedefDecl const* d,
                                     DumpNode const* dn)
{
  this->OS << "  <FundamentalType";
  this->PrintIdAttribute(dn);
  if (d->getName() == "__castxml__float80") {
    this->OS << " name=\"__float80\" size=\"128\" align=\"128\"";
  } else if (d->getName() == "__castxml__float128") {
    this->OS << " name=\"__float128\" size=\"128\" align=\"128\"";
  } else if (d->getName() == "__castxml_Float32") {
    this->OS << " name=\"_Float32\" size=\"32\" align=\"32\"";
  } else if (d->getName() == "__castxml_Float32x") {
    this->OS << " name=\"_Float32x\" size=\"64\" align=\"64\"";
  } else if (d->getName() == "__castxml_Float64") {
    this->OS << " name=\"_Float64\" size=\"64\" align=\"64\"";
  } else if (d->getName() == "__castxml_Float64x") {
    this->OS << " name=\"_Float64x\" size=\"128\" align=\"128\"";
  } else if (d->getName() == "__castxml_Float128") {
    this->OS << " name=\"_Float128\" size=\"128\" align=\"128\"";
  }
  this->OS << "/>\n";
}

bool ASTVisitor::IsCastXMLTypedefType(clang::QualType t) const
{
#if LLVM_VERSION_MAJOR < 22
  if (t->getTypeClass() == clang::Type::Elaborated) {
    auto const* et = static_cast<clang::ElaboratedType const*>(t.getTypePtr());
    t = et->getNamedType();
  }
#endif
  if (t->getTypeClass() == clang::Type::Typedef) {
    auto const* tdt = static_cast<clang::TypedefType const*>(t.getTypePtr());
    if (clang::TypedefDecl const* td =
          clang::dyn_cast<clang::TypedefDecl>(tdt->getDecl())) {
      return this->IsCastXMLTypedefDecl(td);
    }
  }
  return false;
}

bool ASTVisitor::IsCastXMLTypedefDecl(clang::TypedefDecl const* td) const
{
  if (td->getName().starts_with("__castxml") &&
      clang::isa<clang::TranslationUnitDecl>(td->getDeclContext())) {
    clang::SourceLocation sl = td->getLocation();
    if (sl.isValid()) {
      clang::FullSourceLoc fsl = this->CTX.getFullLoc(sl).getExpansionLoc();
      return !this->CI.getSourceManager().getFileEntryForID(fsl.getFileID());
    }
  }
  return false;
}

void ASTVisitor::OutputFunctionHelper(clang::FunctionDecl const* d,
                                      DumpNode const* dn, char const* tag,
                                      unsigned int flags,
                                      cx::optional<std::string> const& name)
{
  this->OS << "  <" << tag;
  this->PrintIdAttribute(dn);
  if (name) {
    this->PrintNameAttribute(*name);
  }
  if (flags & FH_Returns) {
    this->PrintReturnsAttribute(d->getReturnType(), dn->Complete);
  }
  this->PrintContextAttribute(d);
  this->PrintLocationAttribute(d);

  if (flags & FH_Static) {
    this->OS << " static=\"1\"";
  }
  if (flags & FH_Explicit) {
    this->OS << " explicit=\"1\"";
  }
  if (flags & FH_Const) {
    this->OS << " const=\"1\"";
  }
  if (flags & FH_Virtual) {
    this->OS << " virtual=\"1\"";
  }
  if (flags & FH_Pure) {
    this->OS << " pure_virtual=\"1\"";
  }
  if (d->isInlined()) {
    this->OS << " inline=\"1\"";
  }
  if (d->getStorageClass() == clang::SC_Extern) {
    this->OS << " extern=\"1\"";
  }
  if (d->isImplicit()) {
    this->OS << " artificial=\"1\"";
  }

  if (clang::CXXMethodDecl const* md =
        clang::dyn_cast<clang::CXXMethodDecl>(d)) {
    if (md->size_overridden_methods() > 0) {
      this->OS << " overrides=\"";
      char const* sep = "";
      for (clang::CXXMethodDecl::method_iterator
             i = md->begin_overridden_methods(),
             e = md->end_overridden_methods();
           i != e; ++i) {
        if (DumpId id = this->AddDeclDumpNode(*i, false)) {
          this->OS << sep << "_" << id;
          sep = " ";
        }
      }
      this->OS << "\"";
    }
  }

  std::vector<std::string> attributes;

  if (clang::FunctionProtoType const* fpt =
        d->getType()->getAs<clang::FunctionProtoType>()) {
    this->PrintThrowsAttribute(fpt, dn->Complete);
    if (!clang::isa<clang::CXXConstructorDecl>(d) &&
        !clang::isa<clang::CXXDestructorDecl>(d) &&
        d->getLanguageLinkage() == clang::CXXLanguageLinkage) {
      this->PrintMangledAttribute(d);
    }
    this->GetFunctionTypeAttributes(fpt, attributes);
  }

  this->GetDeclAttributes(d, attributes);
  this->PrintAttributesAttribute(attributes);
  this->PrintCommentAttribute(d, dn);

  if (unsigned np = d->getNumParams()) {
    this->OS << ">\n";
    for (unsigned i = 0; i < np; ++i) {
      // Use the default argument from the most recent declaration.
      // Clang accumulates the defaults and only the last one has
      // them all.
      clang::ParmVarDecl const* pd = d->getMostRecentDecl()->getParamDecl(i);
      clang::Expr const* def = pd->getInit();
      if (!def && pd->hasUninstantiatedDefaultArg()) {
        def = pd->getUninstantiatedDefaultArg();
      }

      // Use the parameter located in the canonical declaration.
      this->OutputFunctionArgument(d->getParamDecl(i), dn->Complete, def);
    }
    if (d->isVariadic()) {
      this->OS << "    <Ellipsis/>\n";
    }
    this->OS << "  </" << tag << ">\n";
  } else {
    this->OS << "/>\n";
  }
}

void ASTVisitor::OutputFunctionTypeHelper(clang::FunctionProtoType const* t,
                                          DumpNode const* dn, char const* tag,
                                          clang::Type const* c)
{
  this->OS << "  <" << tag;
  this->PrintIdAttribute(dn);
  if (c) {
    this->PrintBaseTypeAttribute(c, dn->Complete);
  }
  this->PrintReturnsAttribute(t->getReturnType(), dn->Complete);
  if (t->isConst()) {
    this->OS << " const=\"1\"";
  }
  if (t->isVolatile()) {
    this->OS << " volatile=\"1\"";
  }
  if (t->isRestrict()) {
    this->OS << " restrict=\"1\"";
  }
  std::vector<std::string> attributes;
  this->GetFunctionTypeAttributes(t, attributes);
  this->PrintAttributesAttribute(attributes);
  if (t->param_type_begin() != t->param_type_end()) {
    this->OS << ">\n";
    for (clang::FunctionProtoType::param_type_iterator
           i = t->param_type_begin(),
           e = t->param_type_end();
         i != e; ++i) {
      this->OS << "    <Argument";
      this->PrintTypeAttribute(*i, dn->Complete);
      this->OS << "/>\n";
    }
    if (t->isVariadic()) {
      this->OS << "    <Ellipsis/>\n";
    }
    this->OS << "  </" << tag << ">\n";
  } else {
    this->OS << "/>\n";
  }
}

void ASTVisitor::OutputFunctionArgument(clang::ParmVarDecl const* a,
                                        bool complete, clang::Expr const* def)
{
  this->OS << "    <Argument";
  std::string name = a->getName().str();
  if (!name.empty()) {
    this->PrintNameAttribute(name);
  }
  this->PrintTypeAttribute(a->getType(), complete);

  if (a->getOriginalType() != a->getType()) {
    this->OS << " original_type=\"";
    this->PrintTypeIdRef(a->getOriginalType(), complete);
    this->OS << "\"";
  }

  this->PrintLocationAttribute(a);
  if (def) {
    this->OS << " default=\"";
    std::string s;
    llvm::raw_string_ostream rso(s);
    PrinterHelper ph(*this);
    def->printPretty(rso, &ph, this->PrintingPolicy);
    this->OS << encodeXML(rso.str());
    this->OS << "\"";
  }
  this->PrintAttributesAttribute(a);
  this->OS << "/>\n";
}

void ASTVisitor::OutputTranslationUnitDecl(clang::TranslationUnitDecl const* d,
                                           DumpNode const* dn)
{
  this->OS << "  <Namespace";
  this->PrintIdAttribute(dn);
  this->PrintNameAttribute(std::string("::"));
  if (dn->Complete) {
    this->PrintMembersAttribute(d);
  }
  this->OS << "/>\n";
}

void ASTVisitor::OutputNamespaceDecl(clang::NamespaceDecl const* d,
                                     DumpNode const* dn)
{
  this->OS << "  <Namespace";
  this->PrintIdAttribute(dn);
  std::string name = d->getName().str();
  if (!name.empty()) {
    this->PrintNameAttribute(name);
  }
  this->PrintContextAttribute(d);
  if (dn->Complete) {
    std::set<DumpId> emitted;
    for (clang::NamespaceDecl const* r : d->redecls()) {
      this->AddDeclContextMembers(r, emitted);
    }
    this->PrintMembersAttribute(emitted);
  }
  this->PrintCommentAttribute(d, dn);
  this->OS << "/>\n";
}

void ASTVisitor::OutputRecordDecl(clang::RecordDecl const* d,
                                  DumpNode const* dn)
{
  char const* tag;
  switch (d->getTagKind()) {
    case cx_TagTypeKind(Class):
      tag = "Class";
      break;
    case cx_TagTypeKind(Union):
      tag = "Union";
      break;
    case cx_TagTypeKind(Struct):
      tag = "Struct";
      break;
    case cx_TagTypeKind(Interface):
      return;
    case cx_TagTypeKind(Enum):
      return;
  }
  clang::CXXRecordDecl const* dx = clang::dyn_cast<clang::CXXRecordDecl>(d);
  bool doBases = false;

  this->OS << "  <" << tag;
  this->PrintIdAttribute(dn);
  if (!d->isAnonymousStructOrUnion() && !d->isLambda()) {
    std::string s;
    llvm::raw_string_ostream rso(s);
    if (d->getIdentifier()) {
      d->getNameForDiagnostic(rso, this->PrintingPolicy, false);
    }
    this->PrintNameAttribute(rso.str());
  }
  clang::AccessSpecifier access = clang::AS_none;
  if (clang::ClassTemplateSpecializationDecl const* dxts =
        clang::dyn_cast<clang::ClassTemplateSpecializationDecl>(d)) {
    // This is a template instantiation so get the access of the original
    // template.  Access of the instantiation itself has no meaning.
    if (clang::ClassTemplateDecl const* dxt = dxts->getSpecializedTemplate()) {
      access = dxt->getAccess();
    }
  }
  this->PrintContextAttribute(d, access);
  this->PrintLocationAttribute(d);
  if (d->getDefinition()) {
    if (dx && dx->isAbstract()) {
      this->OS << " abstract=\"1\"";
    }
    if (dn->Complete && !d->isInvalidDecl() && !d->isLambda()) {
      this->PrintMembersAttribute(d);
      doBases = dx && dx->getNumBases();
      if (doBases) {
        this->PrintBasesAttribute(dx);
      }
      this->PrintBefriendingAttribute(dx);
    }
  } else {
    this->OS << " incomplete=\"1\"";
  }
  this->PrintABIAttributes(d);
  this->PrintAttributesAttribute(d);
  this->PrintCommentAttribute(d, dn);
  if (doBases) {
    this->OS << ">\n";
    clang::ASTRecordLayout const& layout = this->CTX.getASTRecordLayout(dx);
    for (clang::CXXRecordDecl::base_class_const_iterator i = dx->bases_begin(),
                                                         e = dx->bases_end();
         i != e; ++i) {
      clang::QualType bt = i->getType().getCanonicalType();
      clang::CXXRecordDecl const* bd = clang::dyn_cast<clang::CXXRecordDecl>(
        bt->getAs<clang::RecordType>()->getDecl());
      this->OS << "    <Base";
      this->PrintTypeAttribute(bt, true);
      this->PrintAccessAttribute(i->getAccessSpecifier());
      this->OS << " virtual=\"" << (i->isVirtual() ? 1 : 0) << "\"";
      if (bd && !i->isVirtual()) {
        this->OS << " offset=\"" << layout.getBaseClassOffset(bd).getQuantity()
                 << "\"";
      }
      this->OS << "/>\n";
    }
    this->OS << "  </" << tag << ">\n";
  } else {
    this->OS << "/>\n";
  }
}

void ASTVisitor::OutputCXXRecordDecl(clang::CXXRecordDecl const* d,
                                     DumpNode const* dn)
{
  if (d->getDescribedClassTemplate()) {
    // We do not implement class template output yet.
    this->ASTVisitorBase::OutputCXXRecordDecl(d, dn);
    return;
  }

  this->OutputRecordDecl(d, dn);
}

void ASTVisitor::OutputClassTemplateSpecializationDecl(
  clang::ClassTemplateSpecializationDecl const* d, DumpNode const* dn)
{
  this->OutputCXXRecordDecl(d, dn);
}

void ASTVisitor::OutputTypedefDecl(clang::TypedefDecl const* d,
                                   DumpNode const* dn)
{
  // As a special case, replace our compatibility Typedef types
  // with FundamentalType to pretend we had a builtin type.
  if (this->IsCastXMLTypedefDecl(d)) {
    this->PrintCastXMLTypedef(d, dn);
    return;
  }

  this->OS << "  <Typedef";
  this->PrintIdAttribute(dn);
  this->PrintNameAttribute(d->getName().str());
  this->PrintTypeAttribute(d->getUnderlyingType(), dn->Complete);
  this->PrintContextAttribute(d);
  this->PrintLocationAttribute(d);
  this->PrintAttributesAttribute(d);
  this->PrintCommentAttribute(d, dn);
  this->OS << "/>\n";
}

void ASTVisitor::OutputTypeAliasDecl(clang::TypeAliasDecl const* d,
                                     DumpNode const* dn)
{
  this->OS << "  <Typedef";
  this->PrintIdAttribute(dn);
  this->PrintNameAttribute(d->getName().str());
  this->PrintTypeAttribute(d->getUnderlyingType(), dn->Complete);
  this->PrintContextAttribute(d);
  this->PrintLocationAttribute(d);
  this->PrintAttributesAttribute(d);
  this->PrintCommentAttribute(d, dn);
  this->OS << "/>\n";
}

void ASTVisitor::OutputEnumDecl(clang::EnumDecl const* d, DumpNode const* dn)
{
  this->OS << "  <Enumeration";
  this->PrintIdAttribute(dn);
  std::string name = d->getName().str();
  if (name.empty()) {
    if (clang::TypedefNameDecl const* td = d->getTypedefNameForAnonDecl()) {
      name = td->getName().str();
    }
  }
  this->PrintNameAttribute(name);
  if (this->Opts.CastXml) {
    this->PrintTypeAttribute(d->getIntegerType(), dn->Complete);
  }
  this->PrintContextAttribute(d);
  this->PrintLocationAttribute(d);
  if (this->Opts.CastXml && d->isScoped()) {
    this->OS << " scoped=\"1\"";
  }
  this->PrintABIAttributes(d);
  this->PrintAttributesAttribute(d);
  this->PrintCommentAttribute(d, dn);
  clang::EnumDecl::enumerator_iterator enum_begin = d->enumerator_begin();
  clang::EnumDecl::enumerator_iterator enum_end = d->enumerator_end();
  if (enum_begin != enum_end) {
    this->OS << ">\n";
    for (clang::EnumDecl::enumerator_iterator i = enum_begin; i != enum_end;
         ++i) {
      clang::EnumConstantDecl const* ecd = *i;
      this->OS << "    <EnumValue";
      this->PrintNameAttribute(ecd->getName());
      this->OS << " init=\"" << ecd->getInitVal() << "\"";
      this->PrintAttributesAttribute(ecd);
      this->OS << "/>\n";
    }
    this->OS << "  </Enumeration>\n";
  } else {
    this->OS << "/>\n";
  }
}

void ASTVisitor::OutputFieldDecl(clang::FieldDecl const* d, DumpNode const* dn)
{
  this->OS << "  <Field";
  this->PrintIdAttribute(dn);
  this->PrintNameAttribute(d->getName().str());
  this->PrintTypeAttribute(d->getType(), dn->Complete);
  if (d->isBitField()) {
    unsigned bits = d->getBitWidthValue(
#if LLVM_VERSION_MAJOR < 20
      this->CTX
#endif
    );
    this->OS << " bits=\"" << bits << "\"";
  }
  if (this->Opts.CastXml && !this->IsCastXMLTypedefType(d->getType())) {
    this->PrintInitAttribute(d->getInClassInitializer());
  }
  this->PrintContextAttribute(d);
  this->PrintLocationAttribute(d);
  this->PrintOffsetAttribute(this->CTX.getFieldOffset(d));
  if (d->isMutable()) {
    this->OS << " mutable=\"1\"";
  }
  this->PrintAttributesAttribute(d);
  this->PrintCommentAttribute(d, dn);

  this->OS << "/>\n";
}

void ASTVisitor::OutputVarDecl(clang::VarDecl const* d, DumpNode const* dn)
{
  this->OS << "  <Variable";
  this->PrintIdAttribute(dn);
  this->PrintNameAttribute(d->getName().str());
  this->PrintTypeAttribute(d->getType(), dn->Complete);
  if (!this->IsCastXMLTypedefType(d->getType())) {
    this->PrintInitAttribute(d->getInit());
  }
  this->PrintContextAttribute(d);
  this->PrintLocationAttribute(d);
  if (d->getStorageClass() == clang::SC_Static) {
    this->OS << " static=\"1\"";
  }
  if (d->getStorageClass() == clang::SC_Extern) {
    this->OS << " extern=\"1\"";
  }

  bool const isTranslationUnit =
    clang::isa<clang::TranslationUnitDecl>(d->getDeclContext());
  if (!isTranslationUnit) {
    // FIXME: Recognize 'extern "C" int var;' inside a namespace.
    this->PrintMangledAttribute(d);
  }
  this->PrintAttributesAttribute(d);
  this->PrintCommentAttribute(d, dn);

  this->OS << "/>\n";
}

void ASTVisitor::OutputFunctionDecl(clang::FunctionDecl const* d,
                                    DumpNode const* dn)
{
  if (d->getDescribedFunctionTemplate()) {
    // We do not implement function template output yet.
    this->ASTVisitorBase::OutputFunctionDecl(d, dn);
    return;
  }

  unsigned int flags = FH_Returns;
  if (d->getStorageClass() == clang::SC_Static) {
    flags |= FH_Static;
  }
  if (d->isOverloadedOperator()) {
    this->OutputFunctionHelper(
      d, dn, "OperatorFunction", flags,
      std::string(clang::getOperatorSpelling(d->getOverloadedOperator())));
  } else if (clang::IdentifierInfo const* ii = d->getIdentifier()) {
    this->OutputFunctionHelper(d, dn, "Function", flags, ii->getName().str());
  } else {
    this->OutputUnimplementedDecl(d, dn);
  }
}

void ASTVisitor::OutputCXXMethodDecl(clang::CXXMethodDecl const* d,
                                     DumpNode const* dn)
{
  if (d->getDescribedFunctionTemplate()) {
    // We do not implement function template output yet.
    this->ASTVisitorBase::OutputCXXMethodDecl(d, dn);
    return;
  }

  unsigned int flags = FH_Returns;
  if (d->isStatic()) {
    flags |= FH_Static;
  }
  if (d->isConst()) {
    flags |= FH_Const;
  }
  if (d->isVirtual()) {
    flags |= FH_Virtual;
  }
  if (d->isPureVirtual()) {
    flags |= FH_Pure;
  }
  if (d->isOverloadedOperator()) {
    this->OutputFunctionHelper(
      d, dn, "OperatorMethod", flags,
      std::string(clang::getOperatorSpelling(d->getOverloadedOperator())));
  } else if (clang::IdentifierInfo const* ii = d->getIdentifier()) {
    this->OutputFunctionHelper(d, dn, "Method", flags, ii->getName().str());
  } else {
    this->OutputUnimplementedDecl(d, dn);
  }
}

void ASTVisitor::OutputCXXConversionDecl(clang::CXXConversionDecl const* d,
                                         DumpNode const* dn)
{
  if (d->getDescribedFunctionTemplate()) {
    // We do not implement function template output yet.
    this->ASTVisitorBase::OutputCXXConversionDecl(d, dn);
    return;
  }

  unsigned int flags = FH_Returns;
  if (d->isConst()) {
    flags |= FH_Const;
  }
  if (d->isVirtual()) {
    flags |= FH_Virtual;
  }
  if (d->isPureVirtual()) {
    flags |= FH_Pure;
  }
  this->OutputFunctionHelper(d, dn, "Converter", flags);
}

void ASTVisitor::OutputCXXConstructorDecl(clang::CXXConstructorDecl const* d,
                                          DumpNode const* dn)
{
  if (d->getDescribedFunctionTemplate()) {
    // We do not implement function template output yet.
    this->ASTVisitorBase::OutputCXXConstructorDecl(d, dn);
    return;
  }

  unsigned int flags = 0;
  if (d->isExplicit()) {
    flags |= FH_Explicit;
  }
  this->OutputFunctionHelper(d, dn, "Constructor", flags,
                             this->GetContextName(d));
}

void ASTVisitor::OutputCXXDestructorDecl(clang::CXXDestructorDecl const* d,
                                         DumpNode const* dn)
{
  if (d->getDescribedFunctionTemplate()) {
    // We do not implement function template output yet.
    this->ASTVisitorBase::OutputCXXDestructorDecl(d, dn);
    return;
  }

  unsigned int flags = 0;
  if (d->isVirtual()) {
    flags |= FH_Virtual;
  }
  if (d->isPureVirtual()) {
    flags |= FH_Pure;
  }
  this->OutputFunctionHelper(d, dn, "Destructor", flags,
                             this->GetContextName(d));
}

void ASTVisitor::OutputAtomicType(clang::AtomicType const* t,
                                  DumpNode const* dn)
{
  if (this->Opts.GccXml) {
    this->OutputUnimplementedType(t, dn);
    return;
  }
  this->OS << "  <AtomicType";
  this->PrintIdAttribute(dn);
  this->PrintTypeAttribute(t->getValueType(), false);
  this->PrintABIAttributes(this->CTX.getTypeInfo(t));
  this->OS << "/>\n";
}

void ASTVisitor::OutputAutoType(clang::AutoType const* t, DumpNode const* dn)
{
  if (this->Opts.GccXml) {
    this->OutputUnimplementedType(t, dn);
    return;
  }
  this->OS << "  <AutoType";
  this->PrintIdAttribute(dn);
  this->OS << "/>\n";
}

void ASTVisitor::OutputBuiltinType(clang::BuiltinType const* t,
                                   DumpNode const* dn)
{
  this->OS << "  <FundamentalType";
  this->PrintIdAttribute(dn);

  // gccxml used different name variants than Clang for some types
  std::string name;
  switch (t->getKind()) {
    case clang::BuiltinType::Short:
      name = "short int";
      break;
    case clang::BuiltinType::UShort:
      name = "short unsigned int";
      break;
    case clang::BuiltinType::Long:
      name = "long int";
      break;
    case clang::BuiltinType::ULong:
      name = "long unsigned int";
      break;
    case clang::BuiltinType::LongLong:
      name = "long long int";
      break;
    case clang::BuiltinType::ULongLong:
      name = "long long unsigned int";
      break;
    case clang::BuiltinType::NullPtr:
      name = "decltype(nullptr)";
      break;
    default:
      name = t->getName(this->PrintingPolicy).str();
      break;
  };
  this->PrintNameAttribute(name);
  this->PrintABIAttributes(this->CTX.getTypeInfo(t));

  this->OS << "/>\n";
}

void ASTVisitor::OutputConstantArrayType(clang::ConstantArrayType const* t,
                                         DumpNode const* dn)
{
  this->OS << "  <ArrayType";
  this->PrintIdAttribute(dn);
  this->OS << " min=\"0\" max=\"" << (t->getSize() - 1) << "\"";
  this->PrintTypeAttribute(t->getElementType(), dn->Complete);
  this->OS << "/>\n";
}

void ASTVisitor::OutputIncompleteArrayType(clang::IncompleteArrayType const* t,
                                           DumpNode const* dn)
{
  this->OS << "  <ArrayType";
  this->PrintIdAttribute(dn);
  this->OS << " min=\"0\" max=\"\"";
  this->PrintTypeAttribute(t->getElementType(), dn->Complete);
  this->OS << "/>\n";
}

void ASTVisitor::OutputFunctionProtoType(clang::FunctionProtoType const* t,
                                         DumpNode const* dn)
{
  this->OutputFunctionTypeHelper(t, dn, "FunctionType", 0);
}

void ASTVisitor::OutputLValueReferenceType(clang::LValueReferenceType const* t,
                                           DumpNode const* dn)
{
  this->OS << "  <ReferenceType";
  this->PrintIdAttribute(dn);
  this->PrintTypeAttribute(t->getPointeeType(), false);
  this->PrintABIAttributes(this->CTX.getTypeInfo(t));
  this->OS << "/>\n";
}

void ASTVisitor::OutputRValueReferenceType(clang::RValueReferenceType const* t,
                                           DumpNode const* dn)
{
  this->OS << "  <RValueReferenceType";
  this->PrintIdAttribute(dn);
  this->PrintTypeAttribute(t->getPointeeType(), false);
  this->PrintABIAttributes(this->CTX.getTypeInfo(t));
  this->OS << "/>\n";
}

void ASTVisitor::OutputMemberPointerType(clang::MemberPointerType const* t,
                                         DumpNode const* dn)
{
  clang::Type const* c =
#if LLVM_VERSION_MAJOR >= 21
    cx::deref(t->getQualifier()).getAsType()
#else
    t->getClass()
#endif
    ;
  if (t->isMemberDataPointerType()) {
    this->OutputOffsetType(t->getPointeeType(), c, dn);
  } else {
    this->OS << "  <PointerType";
    this->PrintIdAttribute(dn);
    DumpId id = this->AddTypeDumpNode(DumpType(t->getPointeeType(), c), false);
    this->OS << " type=\"_" << id << "\"";
    this->OS << "/>\n";
  }
}

void ASTVisitor::OutputMethodType(clang::FunctionProtoType const* t,
                                  clang::Type const* c, DumpNode const* dn)
{
  this->OutputFunctionTypeHelper(t, dn, "MethodType", c);
}

void ASTVisitor::OutputOffsetType(clang::QualType t, clang::Type const* c,
                                  DumpNode const* dn)
{
  this->OS << "  <OffsetType";
  this->PrintIdAttribute(dn);
  this->PrintBaseTypeAttribute(c, dn->Complete);
  this->PrintTypeAttribute(t, dn->Complete);
  this->OS << "/>\n";
}

void ASTVisitor::OutputPointerType(clang::PointerType const* t,
                                   DumpNode const* dn)
{
  this->OS << "  <PointerType";
  this->PrintIdAttribute(dn);
  this->PrintTypeAttribute(t->getPointeeType(), false);
  this->PrintABIAttributes(this->CTX.getTypeInfo(t));
  this->OS << "/>\n";
}

template <typename T>
void ASTVisitor::OutputElaboratedTypeImpl(T const* t, DumpNode const* dn)
{
  assert(this->IsElaboratedType(t));
  this->OS << "  <ElaboratedType";
  this->PrintIdAttribute(dn);

  if (cx::NestedNameSpecifier nns = this->GetElaboratedTypeQualifier(t)) {
    std::string s;
    llvm::raw_string_ostream rso(s);
    cx::deref(nns).print(rso, this->PrintingPolicy);
    this->OS << " qualifier=\"" << encodeXML(rso.str()) << '"';
  }

  clang::ElaboratedTypeKeyword k = this->GetElaboratedTypeKeyword(t);
  if (k != cx_ElaboratedTypeKeyword(None)) {
    this->OS << " keyword=\""
             << encodeXML(clang::TypeWithKeyword::getKeywordName(k).str())
             << '"';
  }

  this->PrintTypeAttribute(this->GetElaboratedTypeNamed(t), dn->Complete);
  this->OS << "/>\n";
}

void ASTVisitor::OutputStartXMLTags()
{
  /* clang-format off */
  this->OS <<
    "<?xml version=\"1.0\"?>\n"
    ;
  /* clang-format on */
  if (this->Opts.CastXml) {
    // Start dump with castxml-compatible format.
    /* clang-format off */
    this->OS <<
      "<CastXML format=\"" << Opts.CastXmlEpicFormatVersion << ".4.0\">\n"
      ;
    /* clang-format on */
  } else if (this->Opts.GccXml) {
    // Start dump with gccxml-compatible format (legacy).
    /* clang-format off */
    this->OS <<
      "<GCC_XML version=\"0.9.0\" cvs_revision=\"1.145\">\n"
      ;
    /* clang-format on */
  }
}

void ASTVisitor::OutputEndXMLTags()
{
  // Finish dump.
  if (this->Opts.CastXml) {
    /* clang-format off */
    this->OS <<
      "</CastXML>\n"
      ;
    /* clang-format on */
  } else if (this->Opts.GccXml) {
    /* clang-format off */
    this->OS <<
      "</GCC_XML>\n"
      ;
    /* clang-format on */
  }
}

void ASTVisitor::LookupStart(clang::DeclContext const* dc,
                             std::string const& name)
{
  std::string::size_type pos = name.find("::");
  std::string cur = name.substr(0, pos);

  clang::IdentifierTable& ids = CI.getPreprocessor().getIdentifierTable();
  auto const& result = dc->lookup(clang::DeclarationName(&ids.get(cur)));
  if (pos == name.npos) {
    for (clang::NamedDecl const* n : result) {
      this->AddStartDecl(n);
    }
  } else {
    std::string rest = name.substr(pos + 2);
    for (clang::NamedDecl const* n : result) {
      if (clang::DeclContext const* idc =
            clang::dyn_cast<clang::DeclContext const>(n)) {
        this->LookupStart(idc, rest);
      }
    }
  }

  for (clang::UsingDirectiveDecl const* i : dc->using_directives()) {
    this->LookupStart(i->getNominatedNamespace(), name);
  }
}

void ASTVisitor::HandleTranslationUnit(clang::TranslationUnitDecl const* tu)
{
  // Add the starting nodes for the dump.
  if (!this->Opts.StartNames.empty()) {
    // Use the specified starting locations.
    for (std::vector<std::string>::const_iterator
           i = this->Opts.StartNames.begin(),
           e = this->Opts.StartNames.end();
         i != e; ++i) {
      this->LookupStart(tu, *i);
    }
  } else {
    // No start specified.  Use whole translation unit.
    this->AddStartDecl(tu);
  }

  // Dump opening tags.
  this->OutputStartXMLTags();

  // Dump the complete nodes.
  this->ProcessQueue();

  // Queue all the incomplete nodes.
  this->RequireComplete = false;
  this->QueueIncompleteDumpNodes();

  // Dump the incomplete nodes.
  this->ProcessQueue();

  // Dump the comment queue.
  this->ProcessCommentQueue();

  // Dump the filename queue.
  this->ProcessFileQueue();

  // Dump end tags.
  this->OutputEndXMLTags();
}

void outputXML(clang::CompilerInstance& ci, clang::ASTContext& ctx,
               llvm::raw_ostream& os, Options const& opts)
{
  ASTVisitor v(ci, ctx, os, opts);
  v.HandleTranslationUnit(ctx.getTranslationUnitDecl());
}
