diff --git a/clang-tools-extra/clangd/AST.h b/clang-tools-extra/clangd/AST.h --- a/clang-tools-extra/clangd/AST.h +++ b/clang-tools-extra/clangd/AST.h @@ -47,6 +47,12 @@ /// "(anonymous struct)" or "(anonymous namespace)". std::string printName(const ASTContext &Ctx, const NamedDecl &ND); +/// Prints template arguments of a decl including enclosing '<' and '>', e.g for +/// a partial specialization like: template struct Foo will +/// return ''. +/// Returns an empty string if type is not a template specialization. +std::string printTemplateArgsAsWritten(const NamedDecl &ND); + /// Gets the symbol ID for a declaration, if possible. llvm::Optional getSymbolID(const Decl *D); diff --git a/clang-tools-extra/clangd/AST.cpp b/clang-tools-extra/clangd/AST.cpp --- a/clang-tools-extra/clangd/AST.cpp +++ b/clang-tools-extra/clangd/AST.cpp @@ -16,10 +16,29 @@ #include "clang/Index/USRGeneration.h" #include "llvm/Support/Casting.h" #include "llvm/Support/ScopedPrinter.h" +#include "llvm/Support/raw_ostream.h" namespace clang { namespace clangd { +namespace { +llvm::Optional> +getTemplateSpecializationArgLocs(const NamedDecl &ND) { + if (auto *Func = llvm::dyn_cast(&ND)) { + if (auto *Args = Func->getTemplateSpecializationArgsAsWritten()) + return Args->arguments(); + } else if (auto *Cls = + llvm::dyn_cast(&ND)) { + if (auto *Args = Cls->getTemplateArgsAsWritten()) + return Args->arguments(); + } else if (auto *Var = llvm::dyn_cast(&ND)) + return Var->getTemplateArgsInfo().arguments(); + // We return None for ClassTemplateSpecializationDecls because it does not + // contain TemplateArgumentLoc information. + return llvm::None; +} +} // namespace + // Returns true if the complete name of decl \p D is spelled in the source code. // This is not the case for: // * symbols formed via macro concatenation, the spelling location will @@ -105,6 +124,29 @@ return "(anonymous)"; } +std::string printTemplateArgsAsWritten(const NamedDecl &ND) { + std::string TemplateArgs; + llvm::raw_string_ostream OS(TemplateArgs); + PrintingPolicy Policy(ND.getASTContext().getLangOpts()); + if (auto Args = getTemplateSpecializationArgLocs(ND)) + printTemplateArgumentList(OS, *Args, Policy); + else if (auto *Cls = llvm::dyn_cast(&ND)) { + if (const TypeSourceInfo *TSI = Cls->getTypeAsWritten()) { + auto STL = TSI->getTypeLoc().getAs(); + llvm::SmallVector ArgLocs; + ArgLocs.reserve(STL.getNumArgs()); + for (unsigned I = 0; I < STL.getNumArgs(); ++I) + ArgLocs.push_back(STL.getArgLoc(I)); + printTemplateArgumentList(OS, ArgLocs, Policy); + } else { + // FIXME: Fix cases when getTypeAsWritten returns null, e.g. friend decls. + printTemplateArgumentList(OS, Cls->getTemplateArgs().asArray(), Policy); + } + } + OS.flush(); + return TemplateArgs; +} + std::string printNamespaceScope(const DeclContext &DC) { for (const auto *Ctx = &DC; Ctx != nullptr; Ctx = Ctx->getParent()) if (const auto *NS = dyn_cast(Ctx)) diff --git a/clang-tools-extra/unittests/clangd/ASTUtilsTests.cpp b/clang-tools-extra/unittests/clangd/ASTUtilsTests.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/unittests/clangd/ASTUtilsTests.cpp @@ -0,0 +1,63 @@ +#include "AST.h" +#include "Annotations.h" +#include "Protocol.h" +#include "SourceCode.h" +#include "TestTU.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace clang { +namespace clangd { +namespace { + +using testing::ElementsAre; + +TEST(ASTUtils, PrintTemplateArgs) { + Annotations Test(R"cpp( + template class Bar {}; + template <> class ^Bar {}; + + template class Z, int Q> struct Foo {}; + template struct ^Foo; + template struct ^Foo {}; + + template class Baz {}; + template <> class ^Baz {}; + template class ^Baz {}; + + template void Foz() {}; + template <> void ^Foz<3, 5, 8>() {}; + + template