[clang-tools-extra] r336119 - [clangd] Implement hover for "auto" and "decltype"
Marc-Andre Laperle via cfe-commits
cfe-commits at lists.llvm.org
Mon Jul 2 09:28:34 PDT 2018
Author: malaperle
Date: Mon Jul 2 09:28:34 2018
New Revision: 336119
URL: http://llvm.org/viewvc/llvm-project?rev=336119&view=rev
Log:
[clangd] Implement hover for "auto" and "decltype"
Summary:
This allows hovering on keywords that refer to deduced types.
This should cover most useful cases. Not covered:
- auto template parameters: Since this can be instantiated with many types,
it would not be practical to show the types.
- Structured binding: This could be done later to show multiple deduced types
in the hover.
- auto:: (part of concepts): Outside the scope of this patch.
Signed-off-by: Marc-Andre Laperle <marc-andre.laperle at ericsson.com>
Subscribers: ilya-biryukov, ioeric, MaskRay, jkorous, cfe-commits
Differential Revision: https://reviews.llvm.org/D48159
Modified:
clang-tools-extra/trunk/clangd/XRefs.cpp
clang-tools-extra/trunk/unittests/clangd/TestTU.cpp
clang-tools-extra/trunk/unittests/clangd/TestTU.h
clang-tools-extra/trunk/unittests/clangd/XRefsTests.cpp
Modified: clang-tools-extra/trunk/clangd/XRefs.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/XRefs.cpp?rev=336119&r1=336118&r2=336119&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/XRefs.cpp (original)
+++ clang-tools-extra/trunk/clangd/XRefs.cpp Mon Jul 2 09:28:34 2018
@@ -12,6 +12,7 @@
#include "SourceCode.h"
#include "URI.h"
#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Index/IndexDataConsumer.h"
#include "clang/Index/IndexingAction.h"
#include "clang/Index/USRGeneration.h"
@@ -516,6 +517,18 @@ static Hover getHoverContents(const Decl
return H;
}
+/// Generate a \p Hover object given the type \p T.
+static Hover getHoverContents(QualType T, ASTContext &ASTCtx) {
+ Hover H;
+ std::string TypeText;
+ llvm::raw_string_ostream OS(TypeText);
+ PrintingPolicy Policy = PrintingPolicyForDecls(ASTCtx.getPrintingPolicy());
+ T.print(OS, Policy);
+ OS.flush();
+ H.contents.value += TypeText;
+ return H;
+}
+
/// Generate a \p Hover object given the macro \p MacroInf.
static Hover getHoverContents(StringRef MacroName) {
Hover H;
@@ -526,6 +539,131 @@ static Hover getHoverContents(StringRef
return H;
}
+namespace {
+/// Computes the deduced type at a given location by visiting the relevant
+/// nodes. We use this to display the actual type when hovering over an "auto"
+/// keyword or "decltype()" expression.
+/// FIXME: This could have been a lot simpler by visiting AutoTypeLocs but it
+/// seems that the AutoTypeLocs that can be visited along with their AutoType do
+/// not have the deduced type set. Instead, we have to go to the appropriate
+/// DeclaratorDecl/FunctionDecl and work our back to the AutoType that does have
+/// a deduced type set. The AST should be improved to simplify this scenario.
+class DeducedTypeVisitor : public RecursiveASTVisitor<DeducedTypeVisitor> {
+ SourceLocation SearchedLocation;
+ llvm::Optional<QualType> DeducedType;
+
+public:
+ DeducedTypeVisitor(SourceLocation SearchedLocation)
+ : SearchedLocation(SearchedLocation) {}
+
+ llvm::Optional<QualType> getDeducedType() { return DeducedType; }
+
+ // Handle auto initializers:
+ //- auto i = 1;
+ //- decltype(auto) i = 1;
+ //- auto& i = 1;
+ bool VisitDeclaratorDecl(DeclaratorDecl *D) {
+ if (!D->getTypeSourceInfo() ||
+ D->getTypeSourceInfo()->getTypeLoc().getLocStart() != SearchedLocation)
+ return true;
+
+ auto DeclT = D->getType();
+ // "auto &" is represented as a ReferenceType containing an AutoType
+ if (const ReferenceType *RT = dyn_cast<ReferenceType>(DeclT.getTypePtr()))
+ DeclT = RT->getPointeeType();
+
+ const AutoType *AT = dyn_cast<AutoType>(DeclT.getTypePtr());
+ if (AT && !AT->getDeducedType().isNull()) {
+ // For auto, use the underlying type because the const& would be
+ // represented twice: written in the code and in the hover.
+ // Example: "const auto I = 1", we only want "int" when hovering on auto,
+ // not "const int".
+ //
+ // For decltype(auto), take the type as is because it cannot be written
+ // with qualifiers or references but its decuded type can be const-ref.
+ DeducedType = AT->isDecltypeAuto() ? DeclT : DeclT.getUnqualifiedType();
+ }
+ return true;
+ }
+
+ // Handle auto return types:
+ //- auto foo() {}
+ //- auto& foo() {}
+ //- auto foo() -> decltype(1+1) {}
+ //- operator auto() const { return 10; }
+ bool VisitFunctionDecl(FunctionDecl *D) {
+ if (!D->getTypeSourceInfo())
+ return true;
+ // Loc of auto in return type (c++14).
+ auto CurLoc = D->getReturnTypeSourceRange().getBegin();
+ // Loc of "auto" in operator auto()
+ if (CurLoc.isInvalid() && dyn_cast<CXXConversionDecl>(D))
+ CurLoc = D->getTypeSourceInfo()->getTypeLoc().getBeginLoc();
+ // Loc of "auto" in function with traling return type (c++11).
+ if (CurLoc.isInvalid())
+ CurLoc = D->getSourceRange().getBegin();
+ if (CurLoc != SearchedLocation)
+ return true;
+
+ auto T = D->getReturnType();
+ // "auto &" is represented as a ReferenceType containing an AutoType.
+ if (const ReferenceType *RT = dyn_cast<ReferenceType>(T.getTypePtr()))
+ T = RT->getPointeeType();
+
+ const AutoType *AT = dyn_cast<AutoType>(T.getTypePtr());
+ if (AT && !AT->getDeducedType().isNull()) {
+ DeducedType = T.getUnqualifiedType();
+ } else { // auto in a trailing return type just points to a DecltypeType.
+ const DecltypeType *DT = dyn_cast<DecltypeType>(T.getTypePtr());
+ if (!DT->getUnderlyingType().isNull())
+ DeducedType = DT->getUnderlyingType();
+ }
+ return true;
+ }
+
+ // Handle non-auto decltype, e.g.:
+ // - auto foo() -> decltype(expr) {}
+ // - decltype(expr);
+ bool VisitDecltypeTypeLoc(DecltypeTypeLoc TL) {
+ if (TL.getBeginLoc() != SearchedLocation)
+ return true;
+
+ // A DecltypeType's underlying type can be another DecltypeType! E.g.
+ // int I = 0;
+ // decltype(I) J = I;
+ // decltype(J) K = J;
+ const DecltypeType *DT = dyn_cast<DecltypeType>(TL.getTypePtr());
+ while (DT && !DT->getUnderlyingType().isNull()) {
+ DeducedType = DT->getUnderlyingType();
+ DT = dyn_cast<DecltypeType>(DeducedType->getTypePtr());
+ }
+ return true;
+ }
+};
+} // namespace
+
+/// Retrieves the deduced type at a given location (auto, decltype).
+llvm::Optional<QualType> getDeducedType(ParsedAST &AST,
+ SourceLocation SourceLocationBeg) {
+ Token Tok;
+ auto &ASTCtx = AST.getASTContext();
+ // Only try to find a deduced type if the token is auto or decltype.
+ if (!SourceLocationBeg.isValid() ||
+ Lexer::getRawToken(SourceLocationBeg, Tok, ASTCtx.getSourceManager(),
+ ASTCtx.getLangOpts(), false) ||
+ !Tok.is(tok::raw_identifier)) {
+ return {};
+ }
+ AST.getPreprocessor().LookUpIdentifierInfo(Tok);
+ if (!(Tok.is(tok::kw_auto) || Tok.is(tok::kw_decltype)))
+ return {};
+
+ DeducedTypeVisitor V(SourceLocationBeg);
+ for (Decl *D : AST.getLocalTopLevelDecls())
+ V.TraverseDecl(D);
+ return V.getDeducedType();
+}
+
Optional<Hover> getHover(ParsedAST &AST, Position Pos) {
const SourceManager &SourceMgr = AST.getASTContext().getSourceManager();
SourceLocation SourceLocationBeg =
@@ -539,6 +677,10 @@ Optional<Hover> getHover(ParsedAST &AST,
if (!Symbols.Decls.empty())
return getHoverContents(Symbols.Decls[0]);
+ auto DeducedType = getDeducedType(AST, SourceLocationBeg);
+ if (DeducedType && !DeducedType->isNull())
+ return getHoverContents(*DeducedType, AST.getASTContext());
+
return None;
}
Modified: clang-tools-extra/trunk/unittests/clangd/TestTU.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/unittests/clangd/TestTU.cpp?rev=336119&r1=336118&r2=336119&view=diff
==============================================================================
--- clang-tools-extra/trunk/unittests/clangd/TestTU.cpp (original)
+++ clang-tools-extra/trunk/unittests/clangd/TestTU.cpp Mon Jul 2 09:28:34 2018
@@ -29,6 +29,7 @@ ParsedAST TestTU::build() const {
Cmd.push_back("-include");
Cmd.push_back(FullHeaderName.c_str());
}
+ Cmd.insert(Cmd.end(), ExtraArgs.begin(), ExtraArgs.end());
auto AST = ParsedAST::Build(
createInvocationFromCommandLine(Cmd), nullptr,
MemoryBuffer::getMemBufferCopy(Code),
Modified: clang-tools-extra/trunk/unittests/clangd/TestTU.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/unittests/clangd/TestTU.h?rev=336119&r1=336118&r2=336119&view=diff
==============================================================================
--- clang-tools-extra/trunk/unittests/clangd/TestTU.h (original)
+++ clang-tools-extra/trunk/unittests/clangd/TestTU.h Mon Jul 2 09:28:34 2018
@@ -44,6 +44,9 @@ struct TestTU {
std::string HeaderCode;
std::string HeaderFilename = "TestTU.h";
+ // Extra arguments for the compiler invocation.
+ std::vector<const char *> ExtraArgs;
+
ParsedAST build() const;
SymbolSlab headerSymbols() const;
std::unique_ptr<SymbolIndex> index() const;
Modified: clang-tools-extra/trunk/unittests/clangd/XRefsTests.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/unittests/clangd/XRefsTests.cpp?rev=336119&r1=336118&r2=336119&view=diff
==============================================================================
--- clang-tools-extra/trunk/unittests/clangd/XRefsTests.cpp (original)
+++ clang-tools-extra/trunk/unittests/clangd/XRefsTests.cpp Mon Jul 2 09:28:34 2018
@@ -343,6 +343,13 @@ TEST(Hover, All) {
OneTest Tests[] = {
{
+ R"cpp(// No hover
+ ^int main() {
+ }
+ )cpp",
+ "",
+ },
+ {
R"cpp(// Local variable
int main() {
int bonjour;
@@ -637,16 +644,275 @@ TEST(Hover, All) {
)cpp",
"",
},
+ {
+ R"cpp(// Simple initialization with auto
+ void foo() {
+ ^auto i = 1;
+ }
+ )cpp",
+ "int",
+ },
+ {
+ R"cpp(// Simple initialization with const auto
+ void foo() {
+ const ^auto i = 1;
+ }
+ )cpp",
+ "int",
+ },
+ {
+ R"cpp(// Simple initialization with const auto&
+ void foo() {
+ const ^auto& i = 1;
+ }
+ )cpp",
+ "int",
+ },
+ {
+ R"cpp(// Simple initialization with auto&
+ void foo() {
+ ^auto& i = 1;
+ }
+ )cpp",
+ "int",
+ },
+ {
+ R"cpp(// Auto with initializer list.
+ namespace std
+ {
+ template<class _E>
+ class initializer_list {};
+ }
+ void foo() {
+ ^auto i = {1,2};
+ }
+ )cpp",
+ "class std::initializer_list<int>",
+ },
+ {
+ R"cpp(// User defined conversion to auto
+ struct Bar {
+ operator ^auto() const { return 10; }
+ };
+ )cpp",
+ "int",
+ },
+ {
+ R"cpp(// Simple initialization with decltype(auto)
+ void foo() {
+ ^decltype(auto) i = 1;
+ }
+ )cpp",
+ "int",
+ },
+ {
+ R"cpp(// Simple initialization with const decltype(auto)
+ void foo() {
+ const int j = 0;
+ ^decltype(auto) i = j;
+ }
+ )cpp",
+ "const int",
+ },
+ {
+ R"cpp(// Simple initialization with const& decltype(auto)
+ void foo() {
+ int k = 0;
+ const int& j = k;
+ ^decltype(auto) i = j;
+ }
+ )cpp",
+ "const int &",
+ },
+ {
+ R"cpp(// Simple initialization with & decltype(auto)
+ void foo() {
+ int k = 0;
+ int& j = k;
+ ^decltype(auto) i = j;
+ }
+ )cpp",
+ "int &",
+ },
+ {
+ R"cpp(// decltype with initializer list: nothing
+ namespace std
+ {
+ template<class _E>
+ class initializer_list {};
+ }
+ void foo() {
+ ^decltype(auto) i = {1,2};
+ }
+ )cpp",
+ "",
+ },
+ {
+ R"cpp(// auto function return with trailing type
+ struct Bar {};
+ ^auto test() -> decltype(Bar()) {
+ return Bar();
+ }
+ )cpp",
+ "struct Bar",
+ },
+ {
+ R"cpp(// trailing return type
+ struct Bar {};
+ auto test() -> ^decltype(Bar()) {
+ return Bar();
+ }
+ )cpp",
+ "struct Bar",
+ },
+ {
+ R"cpp(// auto in function return
+ struct Bar {};
+ ^auto test() {
+ return Bar();
+ }
+ )cpp",
+ "struct Bar",
+ },
+ {
+ R"cpp(// auto& in function return
+ struct Bar {};
+ ^auto& test() {
+ return Bar();
+ }
+ )cpp",
+ "struct Bar",
+ },
+ {
+ R"cpp(// const auto& in function return
+ struct Bar {};
+ const ^auto& test() {
+ return Bar();
+ }
+ )cpp",
+ "struct Bar",
+ },
+ {
+ R"cpp(// decltype(auto) in function return
+ struct Bar {};
+ ^decltype(auto) test() {
+ return Bar();
+ }
+ )cpp",
+ "struct Bar",
+ },
+ {
+ R"cpp(// decltype(auto) reference in function return
+ struct Bar {};
+ ^decltype(auto) test() {
+ int a;
+ return (a);
+ }
+ )cpp",
+ "int &",
+ },
+ {
+ R"cpp(// decltype lvalue reference
+ void foo() {
+ int I = 0;
+ ^decltype(I) J = I;
+ }
+ )cpp",
+ "int",
+ },
+ {
+ R"cpp(// decltype lvalue reference
+ void foo() {
+ int I= 0;
+ int &K = I;
+ ^decltype(K) J = I;
+ }
+ )cpp",
+ "int &",
+ },
+ {
+ R"cpp(// decltype lvalue reference parenthesis
+ void foo() {
+ int I = 0;
+ ^decltype((I)) J = I;
+ }
+ )cpp",
+ "int &",
+ },
+ {
+ R"cpp(// decltype rvalue reference
+ void foo() {
+ int I = 0;
+ ^decltype(static_cast<int&&>(I)) J = static_cast<int&&>(I);
+ }
+ )cpp",
+ "int &&",
+ },
+ {
+ R"cpp(// decltype rvalue reference function call
+ int && bar();
+ void foo() {
+ int I = 0;
+ ^decltype(bar()) J = bar();
+ }
+ )cpp",
+ "int &&",
+ },
+ {
+ R"cpp(// decltype of function with trailing return type.
+ struct Bar {};
+ auto test() -> decltype(Bar()) {
+ return Bar();
+ }
+ void foo() {
+ ^decltype(test()) i = test();
+ }
+ )cpp",
+ "struct Bar",
+ },
+ {
+ R"cpp(// decltype of var with decltype.
+ void foo() {
+ int I = 0;
+ decltype(I) J = I;
+ ^decltype(J) K = J;
+ }
+ )cpp",
+ "int",
+ },
+ {
+ R"cpp(// structured binding. Not supported yet
+ struct Bar {};
+ void foo() {
+ Bar a[2];
+ ^auto [x,y] = a;
+ }
+ )cpp",
+ "",
+ },
+ {
+ R"cpp(// Template auto parameter. Nothing (Not useful).
+ template<^auto T>
+ void func() {
+ }
+ void foo() {
+ func<1>();
+ }
+ )cpp",
+ "",
+ },
};
for (const OneTest &Test : Tests) {
Annotations T(Test.Input);
- auto AST = TestTU::withCode(T.code()).build();
+ TestTU TU = TestTU::withCode(T.code());
+ TU.ExtraArgs.push_back("-std=c++17");
+ auto AST = TU.build();
if (auto H = getHover(AST, T.point())) {
EXPECT_NE("", Test.ExpectedHover) << Test.Input;
- EXPECT_EQ(H->contents.value, Test.ExpectedHover) << Test.Input;
+ EXPECT_EQ(H->contents.value, Test.ExpectedHover.str()) << Test.Input;
} else
- EXPECT_EQ("", Test.ExpectedHover) << Test.Input;
+ EXPECT_EQ("", Test.ExpectedHover.str()) << Test.Input;
}
}
More information about the cfe-commits
mailing list