Index: clang-tools-extra/trunk/clangd/XRefs.cpp =================================================================== --- clang-tools-extra/trunk/clangd/XRefs.cpp +++ clang-tools-extra/trunk/clangd/XRefs.cpp @@ -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 @@ 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 @@ 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 { + SourceLocation SearchedLocation; + llvm::Optional DeducedType; + +public: + DeducedTypeVisitor(SourceLocation SearchedLocation) + : SearchedLocation(SearchedLocation) {} + + llvm::Optional 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(DeclT.getTypePtr())) + DeclT = RT->getPointeeType(); + + const AutoType *AT = dyn_cast(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(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(T.getTypePtr())) + T = RT->getPointeeType(); + + const AutoType *AT = dyn_cast(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(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(TL.getTypePtr()); + while (DT && !DT->getUnderlyingType().isNull()) { + DeducedType = DT->getUnderlyingType(); + DT = dyn_cast(DeducedType->getTypePtr()); + } + return true; + } +}; +} // namespace + +/// Retrieves the deduced type at a given location (auto, decltype). +llvm::Optional 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 getHover(ParsedAST &AST, Position Pos) { const SourceManager &SourceMgr = AST.getASTContext().getSourceManager(); SourceLocation SourceLocationBeg = @@ -539,6 +677,10 @@ 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; } Index: clang-tools-extra/trunk/unittests/clangd/TestTU.h =================================================================== --- clang-tools-extra/trunk/unittests/clangd/TestTU.h +++ clang-tools-extra/trunk/unittests/clangd/TestTU.h @@ -44,6 +44,9 @@ std::string HeaderCode; std::string HeaderFilename = "TestTU.h"; + // Extra arguments for the compiler invocation. + std::vector ExtraArgs; + ParsedAST build() const; SymbolSlab headerSymbols() const; std::unique_ptr index() const; Index: clang-tools-extra/trunk/unittests/clangd/TestTU.cpp =================================================================== --- clang-tools-extra/trunk/unittests/clangd/TestTU.cpp +++ clang-tools-extra/trunk/unittests/clangd/TestTU.cpp @@ -29,6 +29,7 @@ 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), Index: clang-tools-extra/trunk/unittests/clangd/XRefsTests.cpp =================================================================== --- clang-tools-extra/trunk/unittests/clangd/XRefsTests.cpp +++ clang-tools-extra/trunk/unittests/clangd/XRefsTests.cpp @@ -343,6 +343,13 @@ OneTest Tests[] = { { + R"cpp(// No hover + ^int main() { + } + )cpp", + "", + }, + { R"cpp(// Local variable int main() { int bonjour; @@ -637,16 +644,275 @@ )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 initializer_list {}; + } + void foo() { + ^auto i = {1,2}; + } + )cpp", + "class std::initializer_list", + }, + { + 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 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(I)) J = static_cast(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; } }