| 1 | //===--- DumpAST.cpp - Serialize clang AST to LSP -------------------------===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | |
| 9 | #include "DumpAST.h" |
| 10 | #include "Protocol.h" |
| 11 | #include "SourceCode.h" |
| 12 | #include "support/Logger.h" |
| 13 | #include "clang/AST/ASTTypeTraits.h" |
| 14 | #include "clang/AST/Expr.h" |
| 15 | #include "clang/AST/ExprCXX.h" |
| 16 | #include "clang/AST/NestedNameSpecifier.h" |
| 17 | #include "clang/AST/PrettyPrinter.h" |
| 18 | #include "clang/AST/RecursiveASTVisitor.h" |
| 19 | #include "clang/AST/TextNodeDumper.h" |
| 20 | #include "clang/AST/Type.h" |
| 21 | #include "clang/AST/TypeLoc.h" |
| 22 | #include "clang/Basic/Specifiers.h" |
| 23 | #include "clang/Tooling/Syntax/Tokens.h" |
| 24 | #include "llvm/ADT/StringRef.h" |
| 25 | #include "llvm/Support/raw_ostream.h" |
| 26 | #include <optional> |
| 27 | |
| 28 | namespace clang { |
| 29 | namespace clangd { |
| 30 | namespace { |
| 31 | |
| 32 | using llvm::raw_ostream; |
| 33 | template <typename Print> std::string toString(const Print &C) { |
| 34 | std::string Result; |
| 35 | llvm::raw_string_ostream OS(Result); |
| 36 | C(OS); |
| 37 | return std::move(OS.str()); |
| 38 | } |
| 39 | |
| 40 | bool isInjectedClassName(Decl *D) { |
| 41 | if (const auto *CRD = llvm::dyn_cast<CXXRecordDecl>(Val: D)) |
| 42 | return CRD->isInjectedClassName(); |
| 43 | return false; |
| 44 | } |
| 45 | |
| 46 | class DumpVisitor : public RecursiveASTVisitor<DumpVisitor> { |
| 47 | using Base = RecursiveASTVisitor<DumpVisitor>; |
| 48 | |
| 49 | const syntax::TokenBuffer &Tokens; |
| 50 | const ASTContext &Ctx; |
| 51 | |
| 52 | // Pointers are into 'children' vector. |
| 53 | // They remain valid because while a node is on the stack we only add |
| 54 | // descendants, not siblings. |
| 55 | std::vector<ASTNode *> Stack; |
| 56 | |
| 57 | // Generic logic used to handle traversal of all node kinds. |
| 58 | |
| 59 | template <typename T> |
| 60 | bool traverseNodePre(llvm::StringRef Role, const T &Node) { |
| 61 | if (Stack.empty()) { |
| 62 | assert(Root.role.empty()); |
| 63 | Stack.push_back(x: &Root); |
| 64 | } else { |
| 65 | Stack.back()->children.emplace_back(); |
| 66 | Stack.push_back(x: &Stack.back()->children.back()); |
| 67 | } |
| 68 | auto &N = *Stack.back(); |
| 69 | N.role = Role.str(); |
| 70 | N.kind = getKind(Node); |
| 71 | N.detail = getDetail(Node); |
| 72 | N.range = getRange(Node); |
| 73 | N.arcana = getArcana(Node); |
| 74 | return true; |
| 75 | } |
| 76 | bool traverseNodePost() { |
| 77 | assert(!Stack.empty()); |
| 78 | Stack.pop_back(); |
| 79 | return true; |
| 80 | } |
| 81 | template <typename T, typename Callable> |
| 82 | bool traverseNode(llvm::StringRef Role, const T &Node, const Callable &Body) { |
| 83 | traverseNodePre(Role, Node); |
| 84 | Body(); |
| 85 | return traverseNodePost(); |
| 86 | } |
| 87 | |
| 88 | // Range: most nodes have getSourceRange(), with a couple of exceptions. |
| 89 | // We only return it if it's valid at both ends and there are no macros. |
| 90 | |
| 91 | template <typename T> std::optional<Range> getRange(const T &Node) { |
| 92 | SourceRange SR = getSourceRange(Node); |
| 93 | auto Spelled = Tokens.spelledForExpanded(Tokens.expandedTokens(R: SR)); |
| 94 | if (!Spelled) |
| 95 | return std::nullopt; |
| 96 | return halfOpenToRange( |
| 97 | Tokens.sourceManager(), |
| 98 | CharSourceRange::getCharRange(Spelled->front().location(), |
| 99 | Spelled->back().endLocation())); |
| 100 | } |
| 101 | template <typename T, typename = decltype(std::declval<T>().getSourceRange())> |
| 102 | SourceRange getSourceRange(const T &Node) { |
| 103 | return Node.getSourceRange(); |
| 104 | } |
| 105 | template <typename T, |
| 106 | typename = decltype(std::declval<T *>()->getSourceRange())> |
| 107 | SourceRange getSourceRange(const T *Node) { |
| 108 | return Node->getSourceRange(); |
| 109 | } |
| 110 | // TemplateName doesn't have a real Loc node type. |
| 111 | SourceRange getSourceRange(const TemplateName &Node) { return SourceRange(); } |
| 112 | // Attr just uses a weird method name. Maybe we should fix it instead? |
| 113 | SourceRange getSourceRange(const Attr *Node) { return Node->getRange(); } |
| 114 | |
| 115 | // Kind is usually the class name, without the suffix ("Type" etc). |
| 116 | // Where there's a set of variants instead, we use the 'Kind' enum values. |
| 117 | |
| 118 | std::string getKind(const Decl *D) { return D->getDeclKindName(); } |
| 119 | std::string getKind(const Stmt *S) { |
| 120 | std::string Result = S->getStmtClassName(); |
| 121 | if (llvm::StringRef(Result).ends_with(Suffix: "Stmt" ) || |
| 122 | llvm::StringRef(Result).ends_with(Suffix: "Expr" )) |
| 123 | Result.resize(n: Result.size() - 4); |
| 124 | return Result; |
| 125 | } |
| 126 | std::string getKind(const TypeLoc &TL) { |
| 127 | if (TL.getTypeLocClass() == TypeLoc::Qualified) |
| 128 | return "Qualified" ; |
| 129 | return TL.getType()->getTypeClassName(); |
| 130 | } |
| 131 | std::string getKind(const TemplateArgumentLoc &TAL) { |
| 132 | switch (TAL.getArgument().getKind()) { |
| 133 | #define TEMPLATE_ARGUMENT_KIND(X) \ |
| 134 | case TemplateArgument::X: \ |
| 135 | return #X |
| 136 | TEMPLATE_ARGUMENT_KIND(Null); |
| 137 | TEMPLATE_ARGUMENT_KIND(NullPtr); |
| 138 | TEMPLATE_ARGUMENT_KIND(Expression); |
| 139 | TEMPLATE_ARGUMENT_KIND(Integral); |
| 140 | TEMPLATE_ARGUMENT_KIND(Pack); |
| 141 | TEMPLATE_ARGUMENT_KIND(Type); |
| 142 | TEMPLATE_ARGUMENT_KIND(Declaration); |
| 143 | TEMPLATE_ARGUMENT_KIND(Template); |
| 144 | TEMPLATE_ARGUMENT_KIND(TemplateExpansion); |
| 145 | TEMPLATE_ARGUMENT_KIND(StructuralValue); |
| 146 | #undef TEMPLATE_ARGUMENT_KIND |
| 147 | } |
| 148 | llvm_unreachable("Unhandled ArgKind enum" ); |
| 149 | } |
| 150 | std::string getKind(const NestedNameSpecifierLoc &NNSL) { |
| 151 | assert(NNSL.getNestedNameSpecifier()); |
| 152 | switch (NNSL.getNestedNameSpecifier()->getKind()) { |
| 153 | #define NNS_KIND(X) \ |
| 154 | case NestedNameSpecifier::X: \ |
| 155 | return #X |
| 156 | NNS_KIND(Identifier); |
| 157 | NNS_KIND(Namespace); |
| 158 | NNS_KIND(TypeSpec); |
| 159 | NNS_KIND(Global); |
| 160 | NNS_KIND(Super); |
| 161 | NNS_KIND(NamespaceAlias); |
| 162 | #undef NNS_KIND |
| 163 | } |
| 164 | llvm_unreachable("Unhandled SpecifierKind enum" ); |
| 165 | } |
| 166 | std::string getKind(const CXXCtorInitializer *CCI) { |
| 167 | if (CCI->isBaseInitializer()) |
| 168 | return "BaseInitializer" ; |
| 169 | if (CCI->isDelegatingInitializer()) |
| 170 | return "DelegatingInitializer" ; |
| 171 | if (CCI->isAnyMemberInitializer()) |
| 172 | return "MemberInitializer" ; |
| 173 | llvm_unreachable("Unhandled CXXCtorInitializer type" ); |
| 174 | } |
| 175 | std::string getKind(const TemplateName &TN) { |
| 176 | switch (TN.getKind()) { |
| 177 | #define TEMPLATE_KIND(X) \ |
| 178 | case TemplateName::X: \ |
| 179 | return #X; |
| 180 | TEMPLATE_KIND(Template); |
| 181 | TEMPLATE_KIND(OverloadedTemplate); |
| 182 | TEMPLATE_KIND(AssumedTemplate); |
| 183 | TEMPLATE_KIND(QualifiedTemplate); |
| 184 | TEMPLATE_KIND(DependentTemplate); |
| 185 | TEMPLATE_KIND(SubstTemplateTemplateParm); |
| 186 | TEMPLATE_KIND(SubstTemplateTemplateParmPack); |
| 187 | TEMPLATE_KIND(UsingTemplate); |
| 188 | TEMPLATE_KIND(DeducedTemplate); |
| 189 | #undef TEMPLATE_KIND |
| 190 | } |
| 191 | llvm_unreachable("Unhandled NameKind enum" ); |
| 192 | } |
| 193 | std::string getKind(const Attr *A) { |
| 194 | switch (A->getKind()) { |
| 195 | #define ATTR(X) \ |
| 196 | case attr::X: \ |
| 197 | return #X; |
| 198 | #include "clang/Basic/AttrList.inc" |
| 199 | #undef ATTR |
| 200 | } |
| 201 | llvm_unreachable("Unhandled attr::Kind enum" ); |
| 202 | } |
| 203 | std::string getKind(const CXXBaseSpecifier &CBS) { |
| 204 | // There aren't really any variants of CXXBaseSpecifier. |
| 205 | // To avoid special cases in the API/UI, use public/private as the kind. |
| 206 | return getAccessSpelling(AS: CBS.getAccessSpecifier()).str(); |
| 207 | } |
| 208 | std::string getKind(const ConceptReference *CR) { |
| 209 | // Again there are no variants here. |
| 210 | // Kind is "Concept", role is "reference" |
| 211 | return "Concept" ; |
| 212 | } |
| 213 | |
| 214 | // Detail is the single most important fact about the node. |
| 215 | // Often this is the name, sometimes a "kind" enum like operators or casts. |
| 216 | // We should avoid unbounded text, like dumping parameter lists. |
| 217 | |
| 218 | std::string getDetail(const Decl *D) { |
| 219 | const auto *ND = dyn_cast<NamedDecl>(D); |
| 220 | if (!ND || llvm::isa_and_nonnull<CXXConstructorDecl>(ND->getAsFunction()) || |
| 221 | isa<CXXDestructorDecl>(ND)) |
| 222 | return "" ; |
| 223 | std::string Name = toString([&](raw_ostream &OS) { ND->printName(OS); }); |
| 224 | if (Name.empty()) |
| 225 | return "(anonymous)" ; |
| 226 | return Name; |
| 227 | } |
| 228 | std::string getDetail(const Stmt *S) { |
| 229 | if (const auto *DRE = dyn_cast<DeclRefExpr>(S)) |
| 230 | return DRE->getNameInfo().getAsString(); |
| 231 | if (const auto *DSDRE = dyn_cast<DependentScopeDeclRefExpr>(S)) |
| 232 | return DSDRE->getNameInfo().getAsString(); |
| 233 | if (const auto *ME = dyn_cast<MemberExpr>(S)) |
| 234 | return ME->getMemberNameInfo().getAsString(); |
| 235 | if (const auto *CE = dyn_cast<CastExpr>(S)) |
| 236 | return CE->getCastKindName(); |
| 237 | if (const auto *BO = dyn_cast<BinaryOperator>(S)) |
| 238 | return BO->getOpcodeStr().str(); |
| 239 | if (const auto *UO = dyn_cast<UnaryOperator>(S)) |
| 240 | return UnaryOperator::getOpcodeStr(Op: UO->getOpcode()).str(); |
| 241 | if (const auto *CCO = dyn_cast<CXXConstructExpr>(S)) |
| 242 | return CCO->getConstructor()->getNameAsString(); |
| 243 | if (const auto *CTE = dyn_cast<CXXThisExpr>(S)) { |
| 244 | bool Const = CTE->getType()->getPointeeType().isLocalConstQualified(); |
| 245 | if (CTE->isImplicit()) |
| 246 | return Const ? "const, implicit" : "implicit" ; |
| 247 | if (Const) |
| 248 | return "const" ; |
| 249 | return "" ; |
| 250 | } |
| 251 | if (isa<IntegerLiteral, FloatingLiteral, FixedPointLiteral, |
| 252 | CharacterLiteral, ImaginaryLiteral, CXXBoolLiteralExpr>(S)) |
| 253 | return toString([&](raw_ostream &OS) { |
| 254 | S->printPretty(OS, Helper: nullptr, Policy: Ctx.getPrintingPolicy()); |
| 255 | }); |
| 256 | if (const auto *MTE = dyn_cast<MaterializeTemporaryExpr>(S)) |
| 257 | return MTE->isBoundToLvalueReference() ? "lvalue" : "rvalue" ; |
| 258 | return "" ; |
| 259 | } |
| 260 | std::string getDetail(const TypeLoc &TL) { |
| 261 | if (TL.getType().hasLocalQualifiers()) |
| 262 | return TL.getType().getLocalQualifiers().getAsString( |
| 263 | Policy: Ctx.getPrintingPolicy()); |
| 264 | if (const auto *TT = dyn_cast<TagType>(TL.getTypePtr())) |
| 265 | return getDetail(TT->getDecl()); |
| 266 | if (const auto *DT = dyn_cast<DeducedType>(TL.getTypePtr())) |
| 267 | if (DT->isDeduced()) |
| 268 | return DT->getDeducedType().getAsString(Ctx.getPrintingPolicy()); |
| 269 | if (const auto *BT = dyn_cast<BuiltinType>(TL.getTypePtr())) |
| 270 | return BT->getName(Ctx.getPrintingPolicy()).str(); |
| 271 | if (const auto *TTPT = dyn_cast<TemplateTypeParmType>(TL.getTypePtr())) |
| 272 | return getDetail(TTPT->getDecl()); |
| 273 | if (const auto *TT = dyn_cast<TypedefType>(TL.getTypePtr())) |
| 274 | return getDetail(TT->getDecl()); |
| 275 | return "" ; |
| 276 | } |
| 277 | std::string getDetail(const NestedNameSpecifierLoc &NNSL) { |
| 278 | const auto &NNS = *NNSL.getNestedNameSpecifier(); |
| 279 | switch (NNS.getKind()) { |
| 280 | case NestedNameSpecifier::Identifier: |
| 281 | return NNS.getAsIdentifier()->getName().str() + "::" ; |
| 282 | case NestedNameSpecifier::Namespace: |
| 283 | return NNS.getAsNamespace()->getNameAsString() + "::" ; |
| 284 | case NestedNameSpecifier::NamespaceAlias: |
| 285 | return NNS.getAsNamespaceAlias()->getNameAsString() + "::" ; |
| 286 | default: |
| 287 | return "" ; |
| 288 | } |
| 289 | } |
| 290 | std::string getDetail(const CXXCtorInitializer *CCI) { |
| 291 | if (FieldDecl *FD = CCI->getAnyMember()) |
| 292 | return getDetail(FD); |
| 293 | if (TypeLoc TL = CCI->getBaseClassLoc()) |
| 294 | return getDetail(TL); |
| 295 | return "" ; |
| 296 | } |
| 297 | std::string getDetail(const TemplateArgumentLoc &TAL) { |
| 298 | if (TAL.getArgument().getKind() == TemplateArgument::Integral) |
| 299 | return toString(I: TAL.getArgument().getAsIntegral(), Radix: 10); |
| 300 | return "" ; |
| 301 | } |
| 302 | std::string getDetail(const TemplateName &TN) { |
| 303 | return toString([&](raw_ostream &OS) { |
| 304 | TN.print(OS, Policy: Ctx.getPrintingPolicy(), Qual: TemplateName::Qualified::None); |
| 305 | }); |
| 306 | } |
| 307 | std::string getDetail(const Attr *A) { |
| 308 | return A->getAttrName() ? A->getNormalizedFullName() : A->getSpelling(); |
| 309 | } |
| 310 | std::string getDetail(const CXXBaseSpecifier &CBS) { |
| 311 | return CBS.isVirtual() ? "virtual" : "" ; |
| 312 | } |
| 313 | std::string getDetail(const ConceptReference *CR) { |
| 314 | return CR->getNamedConcept()->getNameAsString(); |
| 315 | } |
| 316 | |
| 317 | /// Arcana is produced by TextNodeDumper, for the types it supports. |
| 318 | |
| 319 | template <typename Dump> std::string dump(const Dump &D) { |
| 320 | return toString([&](raw_ostream &OS) { |
| 321 | TextNodeDumper Dumper(OS, Ctx, /*ShowColors=*/false); |
| 322 | D(Dumper); |
| 323 | }); |
| 324 | } |
| 325 | template <typename T> std::string getArcana(const T &N) { |
| 326 | return dump([&](TextNodeDumper &D) { D.Visit(N); }); |
| 327 | } |
| 328 | std::string getArcana(const NestedNameSpecifierLoc &NNS) { return "" ; } |
| 329 | std::string getArcana(const TemplateName &NNS) { return "" ; } |
| 330 | std::string getArcana(const CXXBaseSpecifier &CBS) { return "" ; } |
| 331 | std::string getArcana(const TemplateArgumentLoc &TAL) { |
| 332 | return dump([&](TextNodeDumper &D) { |
| 333 | D.Visit(TA: TAL.getArgument(), R: TAL.getSourceRange()); |
| 334 | }); |
| 335 | } |
| 336 | std::string getArcana(const TypeLoc &TL) { |
| 337 | return dump([&](TextNodeDumper &D) { D.Visit(T: TL.getType()); }); |
| 338 | } |
| 339 | |
| 340 | public: |
| 341 | ASTNode Root; |
| 342 | DumpVisitor(const syntax::TokenBuffer &Tokens, const ASTContext &Ctx) |
| 343 | : Tokens(Tokens), Ctx(Ctx) {} |
| 344 | |
| 345 | // Override traversal to record the nodes we care about. |
| 346 | // Generally, these are nodes with position information (TypeLoc, not Type). |
| 347 | |
| 348 | bool TraverseDecl(Decl *D) { |
| 349 | return !D || isInjectedClassName(D) || |
| 350 | traverseNode("declaration" , D, [&] { Base::TraverseDecl(D); }); |
| 351 | } |
| 352 | bool TraverseTypeLoc(TypeLoc TL) { |
| 353 | return !TL || traverseNode("type" , TL, [&] { Base::TraverseTypeLoc(TL); }); |
| 354 | } |
| 355 | bool TraverseTemplateName(const TemplateName &TN) { |
| 356 | return traverseNode("template name" , TN, |
| 357 | [&] { Base::TraverseTemplateName(Template: TN); }); |
| 358 | } |
| 359 | bool TraverseTemplateArgumentLoc(const TemplateArgumentLoc &TAL) { |
| 360 | return traverseNode("template argument" , TAL, |
| 361 | [&] { Base::TraverseTemplateArgumentLoc(ArgLoc: TAL); }); |
| 362 | } |
| 363 | bool TraverseNestedNameSpecifierLoc(NestedNameSpecifierLoc NNSL) { |
| 364 | return !NNSL || traverseNode("specifier" , NNSL, [&] { |
| 365 | Base::TraverseNestedNameSpecifierLoc(NNS: NNSL); |
| 366 | }); |
| 367 | } |
| 368 | bool TraverseConstructorInitializer(CXXCtorInitializer *CCI) { |
| 369 | return !CCI || traverseNode("constructor initializer" , CCI, [&] { |
| 370 | Base::TraverseConstructorInitializer(Init: CCI); |
| 371 | }); |
| 372 | } |
| 373 | bool TraverseAttr(Attr *A) { |
| 374 | return !A || traverseNode("attribute" , A, [&] { Base::TraverseAttr(At: A); }); |
| 375 | } |
| 376 | bool TraverseConceptReference(ConceptReference *C) { |
| 377 | return !C || traverseNode("reference" , C, |
| 378 | [&] { Base::TraverseConceptReference(CR: C); }); |
| 379 | } |
| 380 | bool TraverseCXXBaseSpecifier(const CXXBaseSpecifier &CBS) { |
| 381 | return traverseNode("base" , CBS, |
| 382 | [&] { Base::TraverseCXXBaseSpecifier(Base: CBS); }); |
| 383 | } |
| 384 | // Stmt is the same, but this form allows the data recursion optimization. |
| 385 | bool dataTraverseStmtPre(Stmt *S) { |
| 386 | return S && traverseNodePre(isa<Expr>(S) ? "expression" : "statement" , S); |
| 387 | } |
| 388 | bool dataTraverseStmtPost(Stmt *X) { return traverseNodePost(); } |
| 389 | |
| 390 | // QualifiedTypeLoc is handled strangely in RecursiveASTVisitor: the derived |
| 391 | // TraverseTypeLoc is not called for the inner UnqualTypeLoc. |
| 392 | // This means we'd never see 'int' in 'const int'! Work around that here. |
| 393 | // (The reason for the behavior is to avoid traversing the nested Type twice, |
| 394 | // but we ignore TraverseType anyway). |
| 395 | bool TraverseQualifiedTypeLoc(QualifiedTypeLoc QTL) { |
| 396 | return TraverseTypeLoc(TL: QTL.getUnqualifiedLoc()); |
| 397 | } |
| 398 | // Uninteresting parts of the AST that don't have locations within them. |
| 399 | bool TraverseNestedNameSpecifier(NestedNameSpecifier *) { return true; } |
| 400 | bool TraverseType(QualType) { return true; } |
| 401 | |
| 402 | // OpaqueValueExpr blocks traversal, we must explicitly traverse it. |
| 403 | bool TraverseOpaqueValueExpr(OpaqueValueExpr *E) { |
| 404 | return TraverseStmt(E->getSourceExpr()); |
| 405 | } |
| 406 | // We only want to traverse the *syntactic form* to understand the selection. |
| 407 | bool TraversePseudoObjectExpr(PseudoObjectExpr *E) { |
| 408 | return TraverseStmt(E->getSyntacticForm()); |
| 409 | } |
| 410 | }; |
| 411 | |
| 412 | } // namespace |
| 413 | |
| 414 | ASTNode dumpAST(const DynTypedNode &N, const syntax::TokenBuffer &Tokens, |
| 415 | const ASTContext &Ctx) { |
| 416 | DumpVisitor V(Tokens, Ctx); |
| 417 | // DynTypedNode only works with const, RecursiveASTVisitor only non-const :-( |
| 418 | if (const auto *D = N.get<Decl>()) |
| 419 | V.TraverseDecl(D: const_cast<Decl *>(D)); |
| 420 | else if (const auto *S = N.get<Stmt>()) |
| 421 | V.TraverseStmt(const_cast<Stmt *>(S)); |
| 422 | else if (const auto *NNSL = N.get<NestedNameSpecifierLoc>()) |
| 423 | V.TraverseNestedNameSpecifierLoc( |
| 424 | NNSL: *const_cast<NestedNameSpecifierLoc *>(NNSL)); |
| 425 | else if (const auto *NNS = N.get<NestedNameSpecifier>()) |
| 426 | V.TraverseNestedNameSpecifier(const_cast<NestedNameSpecifier *>(NNS)); |
| 427 | else if (const auto *TL = N.get<TypeLoc>()) |
| 428 | V.TraverseTypeLoc(TL: *const_cast<TypeLoc *>(TL)); |
| 429 | else if (const auto *QT = N.get<QualType>()) |
| 430 | V.TraverseType(*const_cast<QualType *>(QT)); |
| 431 | else if (const auto *CCI = N.get<CXXCtorInitializer>()) |
| 432 | V.TraverseConstructorInitializer(CCI: const_cast<CXXCtorInitializer *>(CCI)); |
| 433 | else if (const auto *TAL = N.get<TemplateArgumentLoc>()) |
| 434 | V.TraverseTemplateArgumentLoc(TAL: *const_cast<TemplateArgumentLoc *>(TAL)); |
| 435 | else if (const auto *CBS = N.get<CXXBaseSpecifier>()) |
| 436 | V.TraverseCXXBaseSpecifier(CBS: *const_cast<CXXBaseSpecifier *>(CBS)); |
| 437 | else if (const auto *CR = N.get<ConceptReference>()) |
| 438 | V.TraverseConceptReference(C: const_cast<ConceptReference *>(CR)); |
| 439 | else |
| 440 | elog("dumpAST: unhandled DynTypedNode kind {0}" , |
| 441 | N.getNodeKind().asStringRef()); |
| 442 | return std::move(V.Root); |
| 443 | } |
| 444 | |
| 445 | } // namespace clangd |
| 446 | } // namespace clang |
| 447 | |