[clangd] Print qualifiers of out-of-line definitions in document outline

Summary: To improve the UX around navigating and searching through the results.

Reviewers: hokein

Reviewed By: hokein

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

Tags: #clang

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

llvm-svn: 368842
This commit is contained in:
Ilya Biryukov 2019-08-14 12:51:04 +00:00
parent d81a869876
commit 38fa1a9168
2 changed files with 128 additions and 35 deletions

View File

@ -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,27 @@ std::string printQualifiedName(const NamedDecl &ND) {
return QName;
}
static bool isAnonymous(const DeclarationName &N) {
return N.isIdentifier() && !N.getAsIdentifierInfo();
}
/// Returns a nested name specifier of \p ND if it was present in the source,
/// e.g.
/// void ns::something::foo() -> returns 'ns::something'
/// void foo() -> returns null
static NestedNameSpecifier *getQualifier(const NamedDecl &ND) {
if (auto *V = llvm::dyn_cast<DeclaratorDecl>(&ND))
return V->getQualifier();
if (auto *T = llvm::dyn_cast<TagDecl>(&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 - <using-directive>.
if (auto *UD = llvm::dyn_cast<UsingDirectiveDecl>(&ND)) {
Out << "using namespace ";
@ -108,19 +128,27 @@ std::string printName(const ASTContext &Ctx, const NamedDecl &ND) {
UD->getNominatedNamespaceAsWritten()->printName(Out);
return Out.str();
}
ND.getDeclName().print(Out, PP);
if (!Out.str().empty()) {
Out << printTemplateSpecializationArgs(ND);
return Out.str();
if (isAnonymous(ND.getDeclName())) {
// Come up with a presentation for an anonymous entity.
if (isa<NamespaceDecl>(ND))
return "(anonymous namespace)";
if (auto *Cls = llvm::dyn_cast<RecordDecl>(&ND))
return ("(anonymous " + Cls->getKindName() + ")").str();
if (isa<EnumDecl>(ND))
return "(anonymous enum)";
return "(anonymous)";
}
// The name was empty, so present an anonymous entity.
if (isa<NamespaceDecl>(ND))
return "(anonymous namespace)";
if (auto *Cls = llvm::dyn_cast<RecordDecl>(&ND))
return ("(anonymous " + Cls->getKindName() + ")").str();
if (isa<EnumDecl>(ND))
return "(anonymous enum)";
return "(anonymous)";
// Print nested name qualifier if it was written in the source code.
if (auto *Qualifier = getQualifier(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) {

View File

@ -19,7 +19,6 @@ namespace clangd {
namespace {
using ::testing::AllOf;
using ::testing::AnyOf;
using ::testing::ElementsAre;
using ::testing::ElementsAreArray;
using ::testing::Field;
@ -414,21 +413,22 @@ TEST_F(DocumentSymbolsTest, BasicSymbols) {
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 @@ TEST_F(DocumentSymbolsTest, DeclarationDefinition) {
)");
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 @@ TEST_F(DocumentSymbolsTest, TempSpecs) {
AllOf(WithName("Foo<bool, int, 3>"), 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 <typename T, typename U = double> class Foo;
template <>
class Foo<int, double> {
int method1();
int method2();
int method3();
};
using int_type = int;
// Typedefs should be preserved!
int Foo<int_type, double>::method1() { return 10; }
// Default arguments should not be shown!
int Foo<int>::method2() { return 20; }
using Foo_type = Foo<int>;
// 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<int, double>"),
WithName("int_type"),
WithName("Foo<int_type, double>::method1"),
WithName("Foo<int>::method2"), WithName("Foo_type"),
WithName("Foo_type::method3")));
}
} // namespace clangd
} // namespace clang