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 @@ -12,6 +12,9 @@ #include "clang/AST/ASTContext.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclTemplate.h" +#include "clang/AST/DeclarationName.h" +#include "clang/AST/NestedNameSpecifier.h" +#include "clang/AST/PrettyPrinter.h" #include "clang/AST/TemplateBase.h" #include "clang/Basic/SourceLocation.h" #include "clang/Basic/SourceManager.h" @@ -96,10 +99,28 @@ return QName; } +static bool isAnonymous(const DeclarationName &N) { + return N.isIdentifier() && !N.getAsIdentifierInfo(); +} + +/// Returns a nested name specifier if \p ND refers to a an out-of-line +/// definition. +/// Examples: +/// void ns::something::foo() -> returns 'ns::something' +/// void foo() -> returns null +static NestedNameSpecifier *getOutOfLineNameQualifier(const NamedDecl &ND) { + if (auto *V = llvm::dyn_cast(&ND)) + return V->getQualifier(); + if (auto *T = llvm::dyn_cast(&ND)) + return T->getQualifier(); + return nullptr; +} + std::string printName(const ASTContext &Ctx, const NamedDecl &ND) { std::string Name; llvm::raw_string_ostream Out(Name); PrintingPolicy PP(Ctx.getLangOpts()); + // Handle 'using namespace'. They all have the same name - . if (auto *UD = llvm::dyn_cast(&ND)) { Out << "using namespace "; @@ -108,19 +129,28 @@ UD->getNominatedNamespaceAsWritten()->printName(Out); return Out.str(); } - ND.getDeclName().print(Out, PP); - if (!Out.str().empty()) { - Out << printTemplateSpecializationArgs(ND); - return Out.str(); + + // Handle anonymous names. + if (isAnonymous(ND.getDeclName())) { + // The name was empty, so present an anonymous entity. + if (isa(ND)) + return "(anonymous namespace)"; + if (auto *Cls = llvm::dyn_cast(&ND)) + return ("(anonymous " + Cls->getKindName() + ")").str(); + if (isa(ND)) + return "(anonymous enum)"; + return "(anonymous)"; } - // The name was empty, so present an anonymous entity. - if (isa(ND)) - return "(anonymous namespace)"; - if (auto *Cls = llvm::dyn_cast(&ND)) - return ("(anonymous " + Cls->getKindName() + ")").str(); - if (isa(ND)) - return "(anonymous enum)"; - return "(anonymous)"; + + // Print nested name qualifier if it was written in the source code. + if (auto *Qualifier = getOutOfLineNameQualifier(ND)) + Qualifier->print(Out, PP); + // Print the name itself. + ND.getDeclName().print(Out, PP); + // Print template arguments. + Out << printTemplateSpecializationArgs(ND); + + return Out.str(); } std::string printTemplateSpecializationArgs(const NamedDecl &ND) { diff --git a/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp b/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp --- a/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp +++ b/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp @@ -19,7 +19,6 @@ namespace { using ::testing::AllOf; -using ::testing::AnyOf; using ::testing::ElementsAre; using ::testing::ElementsAreArray; using ::testing::Field; @@ -414,21 +413,22 @@ AllOf(WithName("KInt"), WithKind(SymbolKind::Variable), Children()), AllOf(WithName("kStr"), WithKind(SymbolKind::Variable), Children()), AllOf(WithName("f1"), WithKind(SymbolKind::Function), Children()), - AllOf(WithName("foo"), WithKind(SymbolKind::Namespace), - Children( - AllOf(WithName("int32"), WithKind(SymbolKind::Class), - Children()), - AllOf(WithName("int32_t"), WithKind(SymbolKind::Class), - Children()), - AllOf(WithName("v1"), WithKind(SymbolKind::Variable), - Children()), - AllOf(WithName("bar"), WithKind(SymbolKind::Namespace), - Children(AllOf(WithName("v2"), - WithKind(SymbolKind::Variable), - Children()))), - AllOf(WithName("baz"), WithKind(SymbolKind::Namespace), - Children()), - AllOf(WithName("v2"), WithKind(SymbolKind::Namespace))))})); + AllOf( + WithName("foo"), WithKind(SymbolKind::Namespace), + Children( + AllOf(WithName("int32"), WithKind(SymbolKind::Class), + Children()), + AllOf(WithName("int32_t"), WithKind(SymbolKind::Class), + Children()), + AllOf(WithName("v1"), WithKind(SymbolKind::Variable), + Children()), + AllOf(WithName("bar"), WithKind(SymbolKind::Namespace), + Children(AllOf(WithName("v2"), + WithKind(SymbolKind::Variable), + Children()))), + AllOf(WithName("baz"), WithKind(SymbolKind::Namespace), + Children()), + AllOf(WithName("v2"), WithKind(SymbolKind::Namespace))))})); } TEST_F(DocumentSymbolsTest, DeclarationDefinition) { @@ -442,13 +442,14 @@ )"); addFile(FilePath, Main.code()); - EXPECT_THAT(getSymbols(FilePath), - ElementsAre(AllOf(WithName("Foo"), WithKind(SymbolKind::Class), - Children(AllOf( - WithName("f"), WithKind(SymbolKind::Method), - SymNameRange(Main.range("decl"))))), - AllOf(WithName("f"), WithKind(SymbolKind::Method), - SymNameRange(Main.range("def"))))); + EXPECT_THAT( + getSymbols(FilePath), + ElementsAre( + AllOf(WithName("Foo"), WithKind(SymbolKind::Class), + Children(AllOf(WithName("f"), WithKind(SymbolKind::Method), + SymNameRange(Main.range("decl"))))), + AllOf(WithName("Foo::f"), WithKind(SymbolKind::Method), + SymNameRange(Main.range("def"))))); } TEST_F(DocumentSymbolsTest, ExternSymbol) { @@ -684,5 +685,69 @@ AllOf(WithName("Foo"), WithKind(SymbolKind::Class)))); } +TEST_F(DocumentSymbolsTest, Qualifiers) { + addFile("foo.cpp", R"cpp( + namespace foo { namespace bar { + struct Cls; + + int func1(); + int func2(); + int func3(); + int func4(); + }} + + struct foo::bar::Cls { }; + + int foo::bar::func1() { return 10; } + int ::foo::bar::func2() { return 20; } + + using namespace foo; + int bar::func3() { return 30; } + + namespace alias = foo::bar; + int ::alias::func4() { return 40; } + )cpp"); + + // All the qualifiers should be preserved exactly as written. + EXPECT_THAT(getSymbols("foo.cpp"), + UnorderedElementsAre( + WithName("foo"), WithName("foo::bar::Cls"), + WithName("foo::bar::func1"), WithName("::foo::bar::func2"), + WithName("using namespace foo"), WithName("bar::func3"), + WithName("alias"), WithName("::alias::func4"))); +} + +TEST_F(DocumentSymbolsTest, QualifiersWithTemplateArgs) { + addFile("foo.cpp", R"cpp( + template class Foo; + + template <> + class Foo { + int method1(); + int method2(); + int method3(); + }; + + using int_type = int; + + // Typedefs should be preserved! + int Foo::method1() { return 10; } + + // Default arguments should not be shown! + int Foo::method2() { return 20; } + + using Foo_type = Foo; + // If the whole type is aliased, this should be preserved too! + int Foo_type::method3() { return 30; } + )cpp"); + EXPECT_THAT( + getSymbols("foo.cpp"), + UnorderedElementsAre(WithName("Foo"), WithName("Foo"), + WithName("int_type"), + WithName("Foo::method1"), + WithName("Foo::method2"), WithName("Foo_type"), + WithName("Foo_type::method3"))); +} + } // namespace clangd } // namespace clang