diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp --- a/clang-tools-extra/clangd/ClangdLSPServer.cpp +++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp @@ -839,7 +839,17 @@ void ClangdLSPServer::onHover(const TextDocumentPositionParams &Params, Callback> Reply) { Server->findHover(Params.textDocument.uri.file(), Params.position, - std::move(Reply)); + Bind( + [](decltype(Reply) Reply, + llvm::Expected> HIorErr) { + if (!HIorErr) + return Reply(HIorErr.takeError()); + const auto &HI = HIorErr.get(); + if (!HI) + return Reply(llvm::None); + return Reply(HI->render()); + }, + std::move(Reply))); } void ClangdLSPServer::onTypeHierarchy( diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h --- a/clang-tools-extra/clangd/ClangdServer.h +++ b/clang-tools-extra/clangd/ClangdServer.h @@ -180,7 +180,7 @@ /// Get code hover for a given position. void findHover(PathRef File, Position Pos, - Callback> CB); + Callback> CB); /// Get information about type hierarchy for a given position. void typeHierarchy(PathRef File, Position Pos, int Resolve, diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp --- a/clang-tools-extra/clangd/ClangdServer.cpp +++ b/clang-tools-extra/clangd/ClangdServer.cpp @@ -523,8 +523,8 @@ } void ClangdServer::findHover(PathRef File, Position Pos, - Callback> CB) { - auto Action = [Pos](Callback> CB, + Callback> CB) { + auto Action = [Pos](Callback> CB, llvm::Expected InpAST) { if (!InpAST) return CB(InpAST.takeError()); diff --git a/clang-tools-extra/clangd/XRefs.h b/clang-tools-extra/clangd/XRefs.h --- a/clang-tools-extra/clangd/XRefs.h +++ b/clang-tools-extra/clangd/XRefs.h @@ -16,7 +16,10 @@ #include "ClangdUnit.h" #include "Protocol.h" #include "index/Index.h" +#include "index/SymbolLocation.h" +#include "clang/Index/IndexSymbol.h" #include "llvm/ADT/Optional.h" +#include "llvm/Support/raw_ostream.h" #include namespace clang { @@ -46,8 +49,40 @@ std::vector findDocumentHighlights(ParsedAST &AST, Position Pos); +struct HoverInfo { + using Type = std::string; + struct Param { + // For template template params it holds the whole template decl, e.g, + // "template class". + // For type-template params will contain "typename" or "class". + Type T; + std::string Name; + std::string Default; + }; + + LocatedSymbol Sym; + /// Fully qualiffied name for the scope containing the Sym. + std::string Scope; + std::string ParentScope; + SymbolKind Kind; + std::string Documentation; + /// Line containing the definition of the symbol. + std::string Definition; + + /// T and ReturnType are mutually exclusive. + llvm::Optional T; + llvm::Optional ReturnType; + llvm::Optional> Parameters; + llvm::Optional> TemplateParameters; + + /// Lower to LSP struct. + Hover render() const; +}; +llvm::raw_ostream &operator<<(llvm::raw_ostream &, const HoverInfo::Param &); +llvm::raw_ostream &operator<<(llvm::raw_ostream &, const HoverInfo &); + /// Get the hover information when hovering at \p Pos. -llvm::Optional getHover(ParsedAST &AST, Position Pos); +llvm::Optional getHover(ParsedAST &AST, Position Pos); /// Returns reference locations of the symbol at a specified \p Pos. /// \p Limit limits the number of results returned (0 means no limit). diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp --- a/clang-tools-extra/clangd/XRefs.cpp +++ b/clang-tools-extra/clangd/XRefs.cpp @@ -7,21 +7,32 @@ //===----------------------------------------------------------------------===// #include "XRefs.h" #include "AST.h" +#include "CodeCompletionStrings.h" #include "FindSymbols.h" #include "Logger.h" +#include "Protocol.h" #include "SourceCode.h" #include "URI.h" #include "index/Merge.h" #include "index/SymbolCollector.h" #include "index/SymbolLocation.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" #include "clang/AST/DeclTemplate.h" +#include "clang/AST/PrettyPrinter.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/AST/Type.h" +#include "clang/Basic/SourceLocation.h" #include "clang/Index/IndexDataConsumer.h" #include "clang/Index/IndexSymbol.h" #include "clang/Index/IndexingAction.h" #include "clang/Index/USRGeneration.h" +#include "llvm/ADT/None.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/FormatVariadic.h" #include "llvm/Support/Path.h" +#include "llvm/Support/raw_ostream.h" namespace clang { namespace clangd { @@ -241,17 +252,17 @@ return {DeclMacrosFinder.getFoundDecls(), DeclMacrosFinder.takeMacroInfos()}; } -Range getTokenRange(ParsedAST &AST, SourceLocation TokLoc) { - const SourceManager &SourceMgr = AST.getASTContext().getSourceManager(); - SourceLocation LocEnd = Lexer::getLocForEndOfToken( - TokLoc, 0, SourceMgr, AST.getASTContext().getLangOpts()); +Range getTokenRange(ASTContext &AST, SourceLocation TokLoc) { + const SourceManager &SourceMgr = AST.getSourceManager(); + SourceLocation LocEnd = + Lexer::getLocForEndOfToken(TokLoc, 0, SourceMgr, AST.getLangOpts()); return {sourceLocToPosition(SourceMgr, TokLoc), sourceLocToPosition(SourceMgr, LocEnd)}; } -llvm::Optional makeLocation(ParsedAST &AST, SourceLocation TokLoc, +llvm::Optional makeLocation(ASTContext &AST, SourceLocation TokLoc, llvm::StringRef TUPath) { - const SourceManager &SourceMgr = AST.getASTContext().getSourceManager(); + const SourceManager &SourceMgr = AST.getSourceManager(); const FileEntry *F = SourceMgr.getFileEntryForID(SourceMgr.getFileID(TokLoc)); if (!F) return None; @@ -299,8 +310,8 @@ // As a consequence, there's no need to look them up in the index either. std::vector Result; for (auto M : Symbols.Macros) { - if (auto Loc = - makeLocation(AST, M.Info->getDefinitionLoc(), *MainFilePath)) { + if (auto Loc = makeLocation(AST.getASTContext(), M.Info->getDefinitionLoc(), + *MainFilePath)) { LocatedSymbol Macro; Macro.Name = M.Name; Macro.PreferredDeclaration = *Loc; @@ -320,7 +331,7 @@ // Emit all symbol locations (declaration or definition) from AST. for (const Decl *D : Symbols.Decls) { - auto Loc = makeLocation(AST, findNameLoc(D), *MainFilePath); + auto Loc = makeLocation(AST.getASTContext(), findNameLoc(D), *MainFilePath); if (!Loc) continue; @@ -453,7 +464,7 @@ std::vector Result; for (const auto &Ref : References) { DocumentHighlight DH; - DH.range = getTokenRange(AST, Ref.Loc); + DH.range = getTokenRange(AST.getASTContext(), Ref.Loc); if (Ref.Role & index::SymbolRoleSet(index::SymbolRole::Write)) DH.kind = DocumentHighlightKind::Write; else if (Ref.Role & index::SymbolRoleSet(index::SymbolRole::Read)) @@ -525,54 +536,208 @@ return None; } -/// Generate a \p Hover object given the declaration \p D. -static Hover getHoverContents(const Decl *D) { - Hover H; - llvm::Optional NamedScope = getScopeName(D); +static QualType getDeclType(const Decl *D) { + if (const TypedefNameDecl *TDD = dyn_cast(D)) + return TDD->getUnderlyingType(); + if (const ValueDecl *VD = dyn_cast(D)) + return VD->getType(); + if (const TagDecl *TD = dyn_cast(D)) + return TD->getTypeForDecl()->getCanonicalTypeInternal(); + return QualType(); +} - // Generate the "Declared in" section. - if (NamedScope) { - assert(!NamedScope->empty()); +static llvm::Optional getDeclLoc(const SourceLocation &SL, + ASTContext &AST) { + if (SL.isInvalid()) + return llvm::None; + const auto &SM = AST.getSourceManager(); + auto MainFilePath = + getCanonicalPath(SM.getFileEntryForID(SM.getMainFileID()), SM); + if (!MainFilePath) { + elog("Couldn't get main file path for: {0}", SL.printToString(SM)); + return llvm::None; + } + return makeLocation(AST, SL, *MainFilePath); +} - H.contents.value += "Declared in "; - H.contents.value += *NamedScope; - H.contents.value += "\n\n"; +static std::string getDefinitionLine(const Decl *D) { + std::string Definition; + { + llvm::raw_string_ostream OS(Definition); + PrintingPolicy Policy = + printingPolicyForDecls(D->getASTContext().getPrintingPolicy()); + D->print(OS, Policy); } + return Definition; +} - // We want to include the template in the Hover. - if (TemplateDecl *TD = D->getDescribedTemplate()) - D = TD; +llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, + const std::vector &Params) { + for (size_t I = 0, E = Params.size(); I != E; ++I) { + if (I) + OS << ", "; + OS << Params.at(I); + } + return OS; +} - std::string DeclText; - llvm::raw_string_ostream OS(DeclText); +static std::vector +fetchTemplateParameters(const TemplateParameterList *Params, + const PrintingPolicy &PP) { + assert(Params); + std::vector TempParameters; + + for (const Decl *Param : *Params) { + HoverInfo::Param P; + if (const auto TTP = dyn_cast(Param)) { + P.T = TTP->wasDeclaredWithTypename() ? "typename" : "class"; + if (TTP->isParameterPack()) + P.T += "..."; + + llvm::raw_string_ostream Out(P.Name); + Out << *TTP; + if (TTP->hasDefaultArgument()) + P.Default = TTP->getDefaultArgument().getAsString(PP); + } else if (const auto NTTP = dyn_cast(Param)) { + if (IdentifierInfo *II = NTTP->getIdentifier()) + P.Name = II->getName().str(); + + llvm::raw_string_ostream Out(P.T); + NTTP->getType().print(Out, PP); + if (NTTP->isParameterPack()) + Out << "..."; + + if (NTTP->hasDefaultArgument()) { + llvm::raw_string_ostream Out(P.Default); + NTTP->getDefaultArgument()->printPretty(Out, nullptr, PP); + } + } else if (const auto TTPD = dyn_cast(Param)) { + llvm::raw_string_ostream OS(P.T); + OS << "template <" + << fetchTemplateParameters( + TTPD->getDescribedTemplate()->getTemplateParameters(), PP) + << "> class"; // FIXME: TemplateTemplateParameter doesn't store the + // info on whether this param was a "typename" or + // "class". + if (TTPD->hasDefaultArgument()) { + llvm::raw_string_ostream Out(P.Default); + TTPD->getDefaultArgument().getArgument().print(PP, Out); + } + } else { + std::string ParamSerial; + llvm::raw_string_ostream Out(ParamSerial); + Param->print(Out, PP); + Out.flush(); + vlog("Unhandled template param: {0}", ParamSerial); + continue; + } + TempParameters.push_back(std::move(P)); + } + + return TempParameters; +} + +/// Generate a \p Hover object given the declaration \p D. +static HoverInfo getHoverContents(const Decl *D) { + HoverInfo HI; PrintingPolicy Policy = printingPolicyForDecls(D->getASTContext().getPrintingPolicy()); + if (const NamedDecl *ND = llvm::dyn_cast(D)) { + HI.Documentation = getDeclComment(ND->getASTContext(), *ND); + HI.Sym.Name = printName(D->getASTContext(), *ND); + HI.Scope = printQualifiedName(*ND); + } - D->print(OS, Policy); + if (llvm::Optional NamedScope = getScopeName(D)) { + assert(!NamedScope->empty()); + HI.ParentScope = std::move(*NamedScope); + } + HI.Kind = indexSymbolKindToSymbolKind(index::getSymbolInfo(D).Kind); - OS.flush(); + if (const TemplateDecl *TD = D->getDescribedTemplate()) { + HI.TemplateParameters = + fetchTemplateParameters(TD->getTemplateParameters(), Policy); + D = TD; + } else if (const FunctionDecl *FD = D->getAsFunction()) { + if (const auto FTD = FD->getDescribedTemplate()) { + HI.TemplateParameters = + fetchTemplateParameters(FTD->getTemplateParameters(), Policy); + D = FTD; + } + } - H.contents.value += DeclText; - return H; + if (const FunctionDecl *FD = D->getAsFunction()) { + HI.ReturnType.emplace(); + llvm::raw_string_ostream OS(*HI.ReturnType); + FD->getReturnType().print(OS, Policy); + + HI.Parameters.emplace(); + for (const ParmVarDecl *PVD : FD->parameters()) { + HI.Parameters->emplace_back(); + auto &P = HI.Parameters->back(); + P.T = PVD->getNameAsString(); + if (PVD->hasDefaultArg()) { + llvm::raw_string_ostream Out(P.Default); + PVD->getDefaultArg()->printPretty(Out, nullptr, Policy); + } + } + if (FD->isVariadic()) { + HI.Parameters->emplace_back(); + HI.Parameters->back().T = "..."; + } + } else { + auto QT = getDeclType(D); + if (!QT.isNull()) { + HI.T.emplace(); + llvm::raw_string_ostream OS(*HI.T); + QT.print(OS, std::move(Policy)); + } + } + + if (auto DefLoc = getDeclLoc(findNameLoc(D), D->getASTContext())) { + HI.Sym.PreferredDeclaration = *DefLoc; + if (getDefinition(D) == D) + HI.Sym.Definition = *DefLoc; + } + HI.Definition = getDefinitionLine(D); + return HI; } /// 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; +static HoverInfo getHoverContents(QualType T, ASTContext &ASTCtx) { + HoverInfo HI; + { + HI.T.emplace(); + llvm::raw_string_ostream OS(*HI.T); + PrintingPolicy Policy = printingPolicyForDecls(ASTCtx.getPrintingPolicy()); + T.print(OS, Policy); + } + HI.Sym.Name = *HI.T; + if (const TagDecl *TD = T.getTypePtr()->getAsTagDecl()) { + if (auto ScopedName = getScopeName(TD)) + HI.ParentScope = std::move(*ScopedName); + HI.Scope = printQualifiedName(*TD); + HI.Kind = indexSymbolKindToSymbolKind(index::getSymbolInfo(TD).Kind); + // FIXME: Populate documentation + if (auto DefLoc = getDeclLoc(findNameLoc(TD), TD->getASTContext())) { + HI.Sym.PreferredDeclaration = *DefLoc; + if (getDefinition(TD) == TD) + HI.Sym.Definition = *DefLoc; + } + HI.Definition = getDefinitionLine(TD); + } + return HI; } /// Generate a \p Hover object given the macro \p MacroDecl. -static Hover getHoverContents(MacroDecl Decl, ParsedAST &AST) { +static HoverInfo getHoverContents(MacroDecl Decl, ParsedAST &AST) { + HoverInfo HI; SourceManager &SM = AST.getASTContext().getSourceManager(); - std::string Definition = Decl.Name; + HI.Sym.Name = Decl.Name; + HI.Kind = indexSymbolKindToSymbolKind( + index::getSymbolInfoForMacro(*Decl.Info).Kind); + // FIXME: Populate documentation // Try to get the full definition, not just the name SourceLocation StartLoc = Decl.Info->getDefinitionLoc(); @@ -586,14 +751,15 @@ unsigned StartOffset = SM.getFileOffset(StartLoc); unsigned EndOffset = SM.getFileOffset(EndLoc); if (EndOffset <= Buffer.size() && StartOffset < EndOffset) - Definition = Buffer.substr(StartOffset, EndOffset - StartOffset).str(); + HI.Definition = + Buffer.substr(StartOffset, EndOffset - StartOffset).str(); } } - - Hover H; - H.contents.kind = MarkupKind::PlainText; - H.contents.value = "#define " + Definition; - return H; + if (auto DefLoc = getDeclLoc(StartLoc, AST.getASTContext())) { + HI.Sym.PreferredDeclaration = *DefLoc; + HI.Sym.Definition = *DefLoc; + } + return HI; } namespace { @@ -708,7 +874,7 @@ return V.getDeducedType(); } -llvm::Optional getHover(ParsedAST &AST, Position Pos) { +llvm::Optional getHover(ParsedAST &AST, Position Pos) { const SourceManager &SourceMgr = AST.getASTContext().getSourceManager(); SourceLocation SourceLocationBeg = getBeginningOfIdentifier(AST, Pos, SourceMgr.getMainFileID()); @@ -748,7 +914,7 @@ auto MainFileRefs = findRefs(Symbols.Decls, AST); for (const auto &Ref : MainFileRefs) { Location Result; - Result.range = getTokenRange(AST, Ref.Loc); + Result.range = getTokenRange(AST.getASTContext(), Ref.Loc); Result.uri = URIForFile::canonicalize(*MainFilePath, *MainFilePath); Results.push_back(std::move(Result)); } @@ -991,5 +1157,70 @@ return Result; } +Hover HoverInfo::render() const { + Hover H; + H.range = Sym.PreferredDeclaration.range; + H.contents.kind = MarkupKind::PlainText; + std::vector Output; + + if (!Scope.empty()) + H.contents.value = llvm::formatv("Declared in {0}\n\n", Scope); + + if (Kind == SymbolKind::String) { + // We use SymbolKind::String for macro kinds + Output.push_back("#define"); + if (!Definition.empty()) + Output.push_back(Definition); + else + Output.push_back(Sym.Name); + } else if (T || ReturnType) { + // Non-macros + if (TemplateParameters) { + Output.emplace_back(); + llvm::raw_string_ostream OS(Output.back()); + OS << "template <" << *TemplateParameters << '>'; + } + if (T) + Output.push_back(*T); + else if (ReturnType) + Output.push_back(*ReturnType); + + if (!Sym.Name.empty()) + if (!T || !llvm::StringRef(*T).endswith(Sym.Name)) + Output.push_back(Sym.Name); + + if (Parameters) { + // We don't want space before parameters. + llvm::raw_string_ostream OS(Output.back()); + OS << '(' << *Parameters << ')'; + } + } else { + // Symbols without types, like namespaces. + Output.push_back(Definition); + } + H.contents.value += llvm::join(Output, " "); + return H; +} + +llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, + const HoverInfo::Param &P) { + OS << P.T << ' ' << P.Name; + if (!P.Default.empty()) + OS << " = " << P.Default; + return OS; +} + +llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const HoverInfo &HI) { + OS << HI.Definition << '\n'; + OS << HI.Documentation << '\n'; + OS << HI.Parameters << '\n'; + OS << HI.ReturnType << '\n'; + OS << HI.Scope << '\n'; + OS << HI.Sym << '\n'; + OS << HI.T << '\n'; + OS << HI.TemplateParameters << '\n'; + return OS; +} + } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/unittests/XRefsTests.cpp b/clang-tools-extra/clangd/unittests/XRefsTests.cpp --- a/clang-tools-extra/clangd/unittests/XRefsTests.cpp +++ b/clang-tools-extra/clangd/unittests/XRefsTests.cpp @@ -1185,7 +1185,9 @@ auto AST = TU.build(); if (auto H = getHover(AST, T.point())) { EXPECT_NE("", Test.ExpectedHover) << Test.Input; - EXPECT_EQ(H->contents.value, Test.ExpectedHover.str()) << Test.Input; + EXPECT_EQ(H->render().contents.value, Test.ExpectedHover.str()) + << Test.Input << '\n' + << H; } else EXPECT_EQ("", Test.ExpectedHover.str()) << Test.Input; }