From bda7d0af970718c243d93b22a8449c20156e574f Mon Sep 17 00:00:00 2001 From: Quentin Chateau Date: Tue, 15 Dec 2020 16:31:25 +0100 Subject: [PATCH] [clangd] Improve goToDefinition on auto and dectype locateSymbolAt (used in goToDeclaration) follows the deduced type instead of failing to locate the declaration. Reviewed By: sammccall Differential Revision: https://reviews.llvm.org/D92977 --- clang-tools-extra/clangd/XRefs.cpp | 70 +++++++++-- clang-tools-extra/clangd/unittests/ASTTests.cpp | 147 +++++++++++++++++++++- clang-tools-extra/clangd/unittests/XRefsTests.cpp | 128 +++++++++++++++++++ 3 files changed, 335 insertions(+), 10 deletions(-) diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp index ac45430..c7ec401 100644 --- a/clang-tools-extra/clangd/XRefs.cpp +++ b/clang-tools-extra/clangd/XRefs.cpp @@ -463,6 +463,42 @@ locateASTReferent(SourceLocation CurLoc, const syntax::Token *TouchedIdentifier, return Result; } +std::vector locateSymbolForType(const ParsedAST &AST, + const QualType &Type) { + const auto &SM = AST.getSourceManager(); + auto MainFilePath = + getCanonicalPath(SM.getFileEntryForID(SM.getMainFileID()), SM); + if (!MainFilePath) { + elog("Failed to get a path for the main file, so no symbol."); + return {}; + } + + auto Decls = targetDecl(DynTypedNode::create(Type.getNonReferenceType()), + DeclRelation::TemplatePattern | DeclRelation::Alias); + if (Decls.empty()) + return {}; + + std::vector Results; + const auto &ASTContext = AST.getASTContext(); + + for (const NamedDecl *D : Decls) { + D = getPreferredDecl(D); + + auto Loc = makeLocation(ASTContext, nameLocation(*D, SM), *MainFilePath); + if (!Loc) + continue; + + Results.emplace_back(); + Results.back().Name = printName(ASTContext, *D); + Results.back().PreferredDeclaration = *Loc; + if (const NamedDecl *Def = getDefinition(D)) + Results.back().Definition = + makeLocation(ASTContext, nameLocation(*Def, SM), *MainFilePath); + } + + return Results; +} + bool tokenSpelledAt(SourceLocation SpellingLoc, const syntax::TokenBuffer &TB) { auto ExpandedTokens = TB.expandedTokens( TB.sourceManager().getMacroArgExpandedLocation(SpellingLoc)); @@ -707,15 +743,31 @@ std::vector locateSymbolAt(ParsedAST &AST, Position Pos, return {}; } - const syntax::Token *TouchedIdentifier = - syntax::spelledIdentifierTouching(*CurLoc, AST.getTokens()); - if (TouchedIdentifier) - if (auto Macro = - locateMacroReferent(*TouchedIdentifier, AST, *MainFilePath)) - // Don't look at the AST or index if we have a macro result. - // (We'd just return declarations referenced from the macro's - // expansion.) - return {*std::move(Macro)}; + const syntax::Token *TouchedIdentifier = nullptr; + auto TokensTouchingCursor = + syntax::spelledTokensTouching(*CurLoc, AST.getTokens()); + for (const syntax::Token &Tok : TokensTouchingCursor) { + if (Tok.kind() == tok::identifier) { + if (auto Macro = locateMacroReferent(Tok, AST, *MainFilePath)) + // Don't look at the AST or index if we have a macro result. + // (We'd just return declarations referenced from the macro's + // expansion.) + return {*std::move(Macro)}; + + TouchedIdentifier = &Tok; + break; + } + + if (Tok.kind() == tok::kw_auto || Tok.kind() == tok::kw_decltype) { + // go-to-definition on auto should find the definition of the deduced + // type, if possible + if (auto Deduced = getDeducedType(AST.getASTContext(), Tok.location())) { + auto LocSym = locateSymbolForType(AST, *Deduced); + if (!LocSym.empty()) + return LocSym; + } + } + } ASTNodeKind NodeKind; auto ASTResults = locateASTReferent(*CurLoc, TouchedIdentifier, AST, diff --git a/clang-tools-extra/clangd/unittests/ASTTests.cpp b/clang-tools-extra/clangd/unittests/ASTTests.cpp index 21f70dc..4c52c72 100644 --- a/clang-tools-extra/clangd/unittests/ASTTests.cpp +++ b/clang-tools-extra/clangd/unittests/ASTTests.cpp @@ -26,23 +26,168 @@ namespace clang { namespace clangd { namespace { -TEST(GetDeducedType, KwAutoExpansion) { +TEST(GetDeducedType, KwAutoKwDecltypeExpansion) { struct Test { StringRef AnnotatedCode; const char *DeducedType; } Tests[] = { {"^auto i = 0;", "int"}, {"^auto f(){ return 1;};", "int"}, + { + R"cpp( // auto on struct in a namespace + namespace ns1 { struct S {}; } + ^auto v = ns1::S{}; + )cpp", + "struct ns1::S", + }, + { + R"cpp( // decltype on struct + namespace ns1 { struct S {}; } + ns1::S i; + ^decltype(i) j; + )cpp", + "ns1::S", + }, + { + R"cpp(// decltype(auto) on struct& + namespace ns1 { + struct S {}; + } // namespace ns1 + + ns1::S i; + ns1::S& j = i; + ^decltype(auto) k = j; + )cpp", + "struct ns1::S &", + }, + { + R"cpp( // auto on template class + class X; + template class Foo {}; + ^auto v = Foo(); + )cpp", + "class Foo", + }, + { + R"cpp( // auto on initializer list. + namespace std + { + template + class [[initializer_list]] {}; + } + + ^auto i = {1,2}; + )cpp", + "class std::initializer_list", + }, + { + R"cpp( // auto in function return type with trailing return type + struct Foo {}; + ^auto test() -> decltype(Foo()) { + return Foo(); + } + )cpp", + "struct Foo", + }, + { + R"cpp( // decltype in trailing return type + struct Foo {}; + auto test() -> ^decltype(Foo()) { + return Foo(); + } + )cpp", + "struct Foo", + }, + { + R"cpp( // auto in function return type + struct Foo {}; + ^auto test() { + return Foo(); + } + )cpp", + "struct Foo", + }, + { + R"cpp( // auto& in function return type + struct Foo {}; + ^auto& test() { + static Foo x; + return x; + } + )cpp", + "struct Foo", + }, + { + R"cpp( // auto* in function return type + struct Foo {}; + ^auto* test() { + Foo *x; + return x; + } + )cpp", + "struct Foo", + }, + { + R"cpp( // const auto& in function return type + struct Foo {}; + const ^auto& test() { + static Foo x; + return x; + } + )cpp", + "struct Foo", + }, + { + R"cpp( // decltype(auto) in function return (value) + struct Foo {}; + ^decltype(auto) test() { + return Foo(); + } + )cpp", + "struct Foo", + }, + { + R"cpp( // decltype(auto) in function return (ref) + struct Foo {}; + ^decltype(auto) test() { + static Foo x; + return (x); + } + )cpp", + "struct Foo &", + }, + { + R"cpp( // decltype(auto) in function return (const ref) + struct Foo {}; + ^decltype(auto) test() { + static const Foo x; + return (x); + } + )cpp", + "const struct Foo &", + }, + { + R"cpp( // auto on alias + struct Foo {}; + using Bar = Foo; + ^auto x = Bar(); + )cpp", + // FIXME: it'd be nice if this resolved to the alias instead + "struct Foo", + }, }; for (Test T : Tests) { Annotations File(T.AnnotatedCode); auto AST = TestTU::withCode(File.code()).build(); SourceManagerForFile SM("foo.cpp", File.code()); + SCOPED_TRACE(File.code()); + EXPECT_FALSE(File.points().empty()); for (Position Pos : File.points()) { auto Location = sourceLocationInMainFile(SM.get(), Pos); ASSERT_TRUE(!!Location) << llvm::toString(Location.takeError()); auto DeducedType = getDeducedType(AST.getASTContext(), *Location); + ASSERT_TRUE(DeducedType); EXPECT_EQ(DeducedType->getAsString(), T.DeducedType); } } diff --git a/clang-tools-extra/clangd/unittests/XRefsTests.cpp b/clang-tools-extra/clangd/unittests/XRefsTests.cpp index dfe1f6c..26e06da 100644 --- a/clang-tools-extra/clangd/unittests/XRefsTests.cpp +++ b/clang-tools-extra/clangd/unittests/XRefsTests.cpp @@ -640,6 +640,134 @@ TEST(LocateSymbol, All) { struct Fo^o {}; )cpp", + R"cpp(// auto builtin type (not supported) + ^auto x = 42; + )cpp", + + R"cpp(// auto on lambda + auto x = [[[]]]{}; + ^auto y = x; + )cpp", + + R"cpp(// auto on struct + namespace ns1 { + struct [[S1]] {}; + } // namespace ns1 + + ^auto x = ns1::S1{}; + )cpp", + + R"cpp(// decltype on struct + namespace ns1 { + struct [[S1]] {}; + } // namespace ns1 + + ns1::S1 i; + ^decltype(i) j; + )cpp", + + R"cpp(// decltype(auto) on struct + namespace ns1 { + struct [[S1]] {}; + } // namespace ns1 + + ns1::S1 i; + ns1::S1& j = i; + ^decltype(auto) k = j; + )cpp", + + R"cpp(// auto on template class + template class [[Foo]] {}; + + ^auto x = Foo(); + )cpp", + + R"cpp(// auto on template class with forward declared class + template class [[Foo]] {}; + class X; + + ^auto x = Foo(); + )cpp", + + R"cpp(// auto on specialized template class + template class Foo {}; + template<> class [[Foo]] {}; + + ^auto x = Foo(); + )cpp", + + R"cpp(// auto on initializer list. + namespace std + { + template + class [[initializer_list]] {}; + } + + ^auto i = {1,2}; + )cpp", + + R"cpp(// auto function return with trailing type + struct [[Bar]] {}; + ^auto test() -> decltype(Bar()) { + return Bar(); + } + )cpp", + + R"cpp(// decltype in trailing return type + struct [[Bar]] {}; + auto test() -> ^decltype(Bar()) { + return Bar(); + } + )cpp", + + R"cpp(// auto in function return + struct [[Bar]] {}; + ^auto test() { + return Bar(); + } + )cpp", + + R"cpp(// auto& in function return + struct [[Bar]] {}; + ^auto& test() { + static Bar x; + return x; + } + )cpp", + + R"cpp(// auto* in function return + struct [[Bar]] {}; + ^auto* test() { + Bar* x; + return x; + } + )cpp", + + R"cpp(// const auto& in function return + struct [[Bar]] {}; + const ^auto& test() { + static Bar x; + return x; + } + )cpp", + + R"cpp(// decltype(auto) in function return + struct [[Bar]] {}; + ^decltype(auto) test() { + return Bar(); + } + )cpp", + + R"cpp(// decltype of function with trailing return type. + struct [[Bar]] {}; + auto test() -> decltype(Bar()) { + return Bar(); + } + void foo() { + ^decltype(test()) i = test(); + } + )cpp", + R"cpp(// Override specifier jumps to overridden method class Y { virtual void $decl[[a]]() = 0; }; class X : Y { void a() ^override {} }; -- 2.7.4