diff --git a/clang-tools-extra/clangd/DumpAST.cpp b/clang-tools-extra/clangd/DumpAST.cpp index cd409a2b930e..2b673128493b 100644 --- a/clang-tools-extra/clangd/DumpAST.cpp +++ b/clang-tools-extra/clangd/DumpAST.cpp @@ -347,8 +347,8 @@ public: }); } bool TraverseTemplateName(const TemplateName &TN) { - return traverseNode("template name", TN, - [&] { Base::TraverseTemplateName(TN); }); + return TN.isNull() || traverseNode("template name", TN, + [&] { Base::TraverseTemplateName(TN); }); } bool TraverseTemplateArgumentLoc(const TemplateArgumentLoc &TAL) { return traverseNode("template argument", TAL, diff --git a/clang-tools-extra/clangd/unittests/DumpASTTests.cpp b/clang-tools-extra/clangd/unittests/DumpASTTests.cpp index 5c857d0b8ae3..7b7e8ae000bf 100644 --- a/clang-tools-extra/clangd/unittests/DumpASTTests.cpp +++ b/clang-tools-extra/clangd/unittests/DumpASTTests.cpp @@ -227,6 +227,35 @@ TEST(DumpASTTests, UnbalancedBraces) { ASSERT_EQ(Node.range, Case.range("func")); } +TEST(DumpASTTests, NestedTemplates) { + // Test that we don't crash while trying to dump AST of a template function + // with nested template names such as Foo::template Bar::Value. + const char *Code = R"cpp( +template +struct TypeA { + template + struct TypeB { + static U Value; + }; +}; + +template +auto func() { + return TypeA::template TypeB::Value; +} + )cpp"; + + ParsedAST AST = TestTU::withCode(Code).build(); + const NamedDecl &Func = findDecl(AST, [](const NamedDecl &D) { + return isa(D) && D.getNameAsString() == "func"; + }); + + const ASTNode Node = + dumpAST(DynTypedNode::create(Func), AST.getTokens(), AST.getASTContext()); + + EXPECT_EQ(Node.kind, "Function"); +} + } // namespace } // namespace clangd } // namespace clang