[clangd] Fix implicit template instatiations appearing as topLevelDecls.

Summary: The parser gives implicit template instantiations to the action's HandleTopLevelDecls callback. This makes semantic highlighting highlight these templated functions multiple times. Fixed by filtering on if a Decl is an implicit function/variable/class instantiation. Also added a testcase to semantic highlighting on this.

Reviewers: hokein, ilya-biryukov

Subscribers: MaskRay, jkorous, arphaman, kadircet, cfe-commits

Tags: #clang

Differential Revision: https://reviews.llvm.org/D65510

llvm-svn: 368261
This commit is contained in:
Johan Vikstrom 2019-08-08 07:21:06 +00:00
parent 67ea32a007
commit 720d19b175
6 changed files with 162 additions and 12 deletions

View File

@ -15,6 +15,7 @@
#include "clang/AST/TemplateBase.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Basic/Specifiers.h"
#include "clang/Index/USRGeneration.h"
#include "llvm/ADT/Optional.h"
#include "llvm/Support/Casting.h"
@ -41,13 +42,57 @@ getTemplateSpecializationArgLocs(const NamedDecl &ND) {
// contain TemplateArgumentLoc information.
return llvm::None;
}
template <class T>
bool isTemplateSpecializationKind(const NamedDecl *D,
TemplateSpecializationKind Kind) {
if (const auto *TD = dyn_cast<T>(D))
return TD->getTemplateSpecializationKind() == Kind;
return false;
}
bool isTemplateSpecializationKind(const NamedDecl *D,
TemplateSpecializationKind Kind) {
return isTemplateSpecializationKind<FunctionDecl>(D, Kind) ||
isTemplateSpecializationKind<CXXRecordDecl>(D, Kind) ||
isTemplateSpecializationKind<VarDecl>(D, Kind);
}
} // namespace
bool isImplicitTemplateInstantiation(const NamedDecl *D) {
return isTemplateSpecializationKind(D, TSK_ImplicitInstantiation);
}
bool isExplicitTemplateSpecialization(const NamedDecl *D) {
return isTemplateSpecializationKind(D, TSK_ExplicitSpecialization);
}
bool isImplementationDetail(const Decl *D) {
return !isSpelledInSource(D->getLocation(),
D->getASTContext().getSourceManager());
}
// 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
// be "<scratch space>"
// * symbols controlled and defined by a compile command-line option
// `-DName=foo`, the spelling location will be "<command line>".
bool isSpelledInSourceCode(const Decl *D) {
const auto &SM = D->getASTContext().getSourceManager();
auto Loc = D->getLocation();
// FIXME: Revisit the strategy, the heuristic is limitted when handling
// macros, we should use the location where the whole definition occurs.
if (Loc.isMacroID()) {
std::string PrintLoc = SM.getSpellingLoc(Loc).printToString(SM);
if (llvm::StringRef(PrintLoc).startswith("<scratch") ||
llvm::StringRef(PrintLoc).startswith("<command line>"))
return false;
}
return true;
}
SourceLocation findName(const clang::Decl *D) {
return D->getLocation();
}

View File

@ -80,9 +80,23 @@ std::string printType(const QualType QT, const DeclContext & Context);
/// take in to account using directives etc
/// Example: shortenNamespace("ns1::MyClass<ns1::OtherClass>", "ns1")
/// --> "MyClass<ns1::OtherClass>"
std::string shortenNamespace(const llvm::StringRef OriginalName,
const llvm::StringRef CurrentNamespace);
std::string shortenNamespace(const llvm::StringRef OriginalName,
const llvm::StringRef CurrentNamespace);
/// Indicates if \p D is a template instantiation implicitly generated by the
/// compiler, e.g.
/// template <class T> struct vector {};
/// vector<int> v; // 'vector<int>' is an implicit instantiation
bool isImplicitTemplateInstantiation(const NamedDecl *D);
/// Indicates if \p D is an explicit template specialization, e.g.
/// template <class T> struct vector {};
/// template <> struct vector<bool> {}; // <-- explicit specialization
///
/// Note that explicit instantiations are NOT explicit specializations, albeit
/// they look similar.
/// template struct vector<bool>; // <-- explicit instantiation, NOT an
/// explicit specialization.
bool isExplicitTemplateSpecialization(const NamedDecl *D);
} // namespace clangd
} // namespace clang

View File

@ -9,6 +9,7 @@
#include "ClangdUnit.h"
#include "../clang-tidy/ClangTidyDiagnosticConsumer.h"
#include "../clang-tidy/ClangTidyModuleRegistry.h"
#include "AST.h"
#include "Compiler.h"
#include "Diagnostics.h"
#include "Headers.h"
@ -19,6 +20,7 @@
#include "index/CanonicalIncludes.h"
#include "index/Index.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Decl.h"
#include "clang/Basic/LangOptions.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Basic/TokenKinds.h"
@ -70,6 +72,9 @@ public:
auto &SM = D->getASTContext().getSourceManager();
if (!isInsideMainFile(D->getLocation(), SM))
continue;
if (const NamedDecl *ND = dyn_cast<NamedDecl>(D))
if (isImplicitTemplateInstantiation(ND))
continue;
// ObjCMethodDecl are not actually top-level decls.
if (isa<ObjCMethodDecl>(D))

View File

@ -1674,13 +1674,6 @@ private:
}
};
template <class T> bool isExplicitTemplateSpecialization(const NamedDecl &ND) {
if (const auto *TD = dyn_cast<T>(&ND))
if (TD->getTemplateSpecializationKind() == TSK_ExplicitSpecialization)
return true;
return false;
}
} // namespace
clang::CodeCompleteOptions CodeCompleteOptions::getClangCompleteOpts() const {
@ -1783,9 +1776,7 @@ bool isIndexedForCodeCompletion(const NamedDecl &ND, ASTContext &ASTCtx) {
};
// We only complete symbol's name, which is the same as the name of the
// *primary* template in case of template specializations.
if (isExplicitTemplateSpecialization<FunctionDecl>(ND) ||
isExplicitTemplateSpecialization<CXXRecordDecl>(ND) ||
isExplicitTemplateSpecialization<VarDecl>(ND))
if (isExplicitTemplateSpecialization(&ND))
return false;
if (InTopLevelScope(ND))

View File

@ -6,13 +6,16 @@
//
//===----------------------------------------------------------------------===//
#include "AST.h"
#include "Annotations.h"
#include "ClangdUnit.h"
#include "SourceCode.h"
#include "TestTU.h"
#include "clang/AST/DeclTemplate.h"
#include "clang/Basic/TokenKinds.h"
#include "clang/Tooling/Syntax/Tokens.h"
#include "llvm/Support/ScopedPrinter.h"
#include "gmock/gmock-matchers.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
@ -21,6 +24,8 @@ namespace clangd {
namespace {
using ::testing::ElementsAre;
using ::testing::ElementsAreArray;
using ::testing::AllOf;
TEST(ClangdUnitTest, GetBeginningOfIdentifier) {
std::string Preamble = R"cpp(
@ -72,6 +77,31 @@ MATCHER_P(DeclNamed, Name, "") {
return false;
}
// Matches if the Decl has template args equal to ArgName. If the decl is a
// NamedDecl and ArgName is an empty string it also matches.
MATCHER_P(WithTemplateArgs, ArgName, "") {
if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(arg)) {
if (const auto *Args = FD->getTemplateSpecializationArgs()) {
std::string SpecializationArgs;
// Without the PrintingPolicy "bool" will be printed as "_Bool".
LangOptions LO;
PrintingPolicy Policy(LO);
Policy.adjustForCPlusPlus();
for (const auto Arg : Args->asArray()) {
if (SpecializationArgs.size() > 0)
SpecializationArgs += ",";
SpecializationArgs += Arg.getAsType().getAsString(Policy);
}
if (Args->size() == 0)
return ArgName == SpecializationArgs;
return ArgName == "<" + SpecializationArgs + ">";
}
}
if (const NamedDecl *ND = dyn_cast<NamedDecl>(arg))
return printTemplateSpecializationArgs(*ND) == ArgName;
return false;
}
TEST(ClangdUnitTest, TopLevelDecls) {
TestTU TU;
TU.HeaderCode = R"(
@ -103,6 +133,65 @@ TEST(ClangdUnitTest, DoesNotGetIncludedTopDecls) {
EXPECT_THAT(AST.getLocalTopLevelDecls(), ElementsAre(DeclNamed("main")));
}
TEST(ClangdUnitTest, DoesNotGetImplicitTemplateTopDecls) {
TestTU TU;
TU.Code = R"cpp(
template<typename T>
void f(T) {}
void s() {
f(10UL);
}
)cpp";
auto AST = TU.build();
EXPECT_THAT(AST.getLocalTopLevelDecls(),
ElementsAre(DeclNamed("f"), DeclNamed("s")));
}
TEST(ClangdUnitTest,
GetsExplicitInstantiationAndSpecializationTemplateTopDecls) {
TestTU TU;
TU.Code = R"cpp(
template <typename T>
void f(T) {}
template<>
void f(bool);
template void f(double);
template <class T>
struct V {};
template<class T>
struct V<T*> {};
template <>
struct V<bool> {};
template<class T>
T foo = T(10);
int i = foo<int>;
double d = foo<double>;
template <class T>
int foo<T*> = 0;
template <>
int foo<bool> = 0;
)cpp";
auto AST = TU.build();
EXPECT_THAT(
AST.getLocalTopLevelDecls(),
ElementsAreArray({AllOf(DeclNamed("f"), WithTemplateArgs("")),
AllOf(DeclNamed("f"), WithTemplateArgs("<bool>")),
AllOf(DeclNamed("f"), WithTemplateArgs("<double>")),
AllOf(DeclNamed("V"), WithTemplateArgs("")),
AllOf(DeclNamed("V"), WithTemplateArgs("<T *>")),
AllOf(DeclNamed("V"), WithTemplateArgs("<bool>")),
AllOf(DeclNamed("foo"), WithTemplateArgs("")),
AllOf(DeclNamed("i"), WithTemplateArgs("")),
AllOf(DeclNamed("d"), WithTemplateArgs("")),
AllOf(DeclNamed("foo"), WithTemplateArgs("<>")),
AllOf(DeclNamed("foo"), WithTemplateArgs("<bool>"))}));
}
TEST(ClangdUnitTest, TokensAfterPreamble) {
TestTU TU;
TU.AdditionalFiles["foo.h"] = R"(

View File

@ -249,6 +249,12 @@ TEST(SemanticHighlighting, GetsCorrectTokens) {
template<typename $TemplateParameter[[T]]>
void $Function[[foo]]($TemplateParameter[[T]] ...);
)cpp",
R"cpp(
template <class $TemplateParameter[[T]]>
struct $Class[[Tmpl]] {$TemplateParameter[[T]] $Field[[x]] = 0;};
extern template struct $Class[[Tmpl]]<float>;
template struct $Class[[Tmpl]]<double>;
)cpp"};
for (const auto &TestCase : TestCases) {
checkHighlightings(TestCase);