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 @@ -842,7 +842,20 @@ 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); + Hover H; + H.range = HI->SymRange; + H.contents = HI->render(); + return Reply(H); + }, + 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 @@ -186,7 +186,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 @@ -461,15 +461,18 @@ } void ClangdServer::findHover(PathRef File, Position Pos, - Callback> CB) { - auto Action = [Pos](Callback> CB, + Callback> CB) { + auto Action = [Pos](Callback> CB, Path File, llvm::Expected InpAST) { if (!InpAST) return CB(InpAST.takeError()); - CB(clangd::getHover(InpAST->AST, Pos)); + format::FormatStyle Style = getFormatStyleForFile( + File, InpAST->Inputs.Contents, InpAST->Inputs.FS.get()); + CB(clangd::getHover(InpAST->AST, Pos, std::move(Style))); }; - WorkScheduler.runWithAST("Hover", File, Bind(Action, std::move(CB))); + WorkScheduler.runWithAST("Hover", File, + Bind(Action, std::move(CB), File.str())); } void ClangdServer::typeHierarchy(PathRef File, Position Pos, int Resolve, diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp --- a/clang-tools-extra/clangd/Protocol.cpp +++ b/clang-tools-extra/clangd/Protocol.cpp @@ -17,6 +17,7 @@ #include "llvm/ADT/Hashing.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringSwitch.h" +#include "llvm/Support/ErrorHandling.h" #include "llvm/Support/Format.h" #include "llvm/Support/FormatVariadic.h" #include "llvm/Support/JSON.h" 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,73 @@ std::vector findDocumentHighlights(ParsedAST &AST, Position Pos); +/// Contains detailed information about a Symbol. Especially useful when +/// generating hover responses. It can be rendered as a hover panel, or +/// embedding clients can use the structured information to provide their own +/// UI. +struct HoverInfo { + /// Represents parameters of a function, a template or a macro. + /// For example: + /// - void foo(ParamType Name = DefaultValue) + /// - #define FOO(Name) + /// - template class Foo {}; + struct Param { + /// The pretty-printed parameter type, e.g. "int", or "typename" (in + /// TemplateParameters) + llvm::Optional Type; + /// None for unnamed parameters. + llvm::Optional Name; + /// None if no default is provided. + llvm::Optional Default; + }; + + /// For a variable named Bar, declared in clang::clangd::Foo::getFoo the + /// following fields will hold: + /// - NamespaceScope: clang::clangd:: + /// - LocalScope: Foo::getFoo:: + /// - Name: Bar + + /// Scopes might be None in cases where they don't make sense, e.g. macros and + /// auto/decltype. + /// Contains all of the enclosing namespaces, empty string means global + /// namespace. + llvm::Optional NamespaceScope; + /// Remaining named contexts in symbol's qualified name, empty string means + /// symbol is not local. + std::string LocalScope; + /// Name of the symbol, does not contain any "::". + std::string Name; + llvm::Optional SymRange; + /// Scope containing the symbol. e.g, "global namespace", "function x::Y" + /// - None for deduced types, e.g "auto", "decltype" keywords. + SymbolKind Kind; + std::string Documentation; + /// Source code containing the definition of the symbol. + std::string Definition; + + /// Pretty-printed variable type. + /// Set only for variables. + llvm::Optional Type; + /// Set for functions and lambadas. + llvm::Optional ReturnType; + /// Set for functions, lambdas and macros with parameters. + llvm::Optional> Parameters; + /// Set for all templates(function, class, variable). + llvm::Optional> TemplateParameters; + + /// Lower to LSP struct. + MarkupContent render() const; +}; +llvm::raw_ostream &operator<<(llvm::raw_ostream &, const HoverInfo::Param &); +inline bool operator==(const HoverInfo::Param &LHS, + const HoverInfo::Param &RHS) { + return std::tie(LHS.Type, LHS.Name, LHS.Default) == + std::tie(RHS.Type, RHS.Name, RHS.Default); +} + /// Get the hover information when hovering at \p Pos. -llvm::Optional getHover(ParsedAST &AST, Position Pos); +llvm::Optional getHover(ParsedAST &AST, Position Pos, + format::FormatStyle Style); /// 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,37 @@ //===----------------------------------------------------------------------===// #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/LLVM.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Basic/SourceManager.h" #include "clang/Index/IndexDataConsumer.h" #include "clang/Index/IndexSymbol.h" #include "clang/Index/IndexingAction.h" #include "clang/Index/USRGeneration.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/None.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/ADT/StringRef.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 +257,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 +315,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 +336,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 +469,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)) @@ -477,102 +493,238 @@ return Policy; } -/// Return a string representation (e.g. "class MyNamespace::MyClass") of -/// the type declaration \p TD. -static std::string typeDeclToString(const TypeDecl *TD) { - QualType Type = TD->getASTContext().getTypeDeclType(TD); +/// Given a declaration \p D, return a human-readable string representing the +/// local scope in which it is declared, i.e. class(es) and method name. Returns +/// an empty string if it is not local. +static std::string getLocalScope(const Decl *D) { + std::vector Scopes; + const DeclContext *DC = D->getDeclContext(); + auto GetName = [](const Decl *D) { + const NamedDecl *ND = dyn_cast(D); + std::string Name = ND->getNameAsString(); + if (!Name.empty()) + return Name; + if (auto RD = dyn_cast(D)) + return ("(anonymous " + RD->getKindName() + ")").str(); + return std::string(""); + }; + while (DC) { + if (const TypeDecl *TD = dyn_cast(DC)) + Scopes.push_back(GetName(TD)); + else if (const FunctionDecl *FD = dyn_cast(DC)) + Scopes.push_back(FD->getNameAsString()); + DC = DC->getParent(); + } - PrintingPolicy Policy = - printingPolicyForDecls(TD->getASTContext().getPrintingPolicy()); + return llvm::join(llvm::reverse(Scopes), "::"); +} - std::string Name; - llvm::raw_string_ostream Stream(Name); - Type.print(Stream, Policy); +/// Returns the human-readable representation for namespace containing the +/// declaration \p D. Returns empty if it is contained global namespace. +static std::string getNamespaceScope(const Decl *D) { + const DeclContext *DC = D->getDeclContext(); + + if (const TypeDecl *TD = dyn_cast(DC)) + return getNamespaceScope(TD); + if (const FunctionDecl *FD = dyn_cast(DC)) + return getNamespaceScope(FD); + if (const NamedDecl *ND = dyn_cast(DC)) + return ND->getQualifiedNameAsString(); - return Stream.str(); + return ""; } -/// Return a string representation (e.g. "namespace ns1::ns2") of -/// the named declaration \p ND. -static std::string namedDeclQualifiedName(const NamedDecl *ND, - llvm::StringRef Prefix) { +static std::string printDefinition(const Decl *D) { + std::string Definition; + llvm::raw_string_ostream OS(Definition); PrintingPolicy Policy = - printingPolicyForDecls(ND->getASTContext().getPrintingPolicy()); - - std::string Name; - llvm::raw_string_ostream Stream(Name); - Stream << Prefix << ' '; - ND->printQualifiedName(Stream, Policy); + printingPolicyForDecls(D->getASTContext().getPrintingPolicy()); + Policy.IncludeTagDefinition = false; + D->print(OS, Policy); + return Definition; +} - return Stream.str(); +static void printParams(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); + } } -/// Given a declaration \p D, return a human-readable string representing the -/// scope in which it is declared. If the declaration is in the global scope, -/// return the string "global namespace". -static llvm::Optional getScopeName(const Decl *D) { - const DeclContext *DC = D->getDeclContext(); +static std::vector +fetchTemplateParameters(const TemplateParameterList *Params, + const PrintingPolicy &PP) { + assert(Params); + std::vector TempParameters; + + for (const Decl *Param : *Params) { + HoverInfo::Param P; + P.Type.emplace(); + if (const auto TTP = dyn_cast(Param)) { + P.Type = TTP->wasDeclaredWithTypename() ? "typename" : "class"; + if (TTP->isParameterPack()) + *P.Type += "..."; + + if (!TTP->getName().empty()) + P.Name = TTP->getNameAsString(); + 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.Type); + NTTP->getType().print(Out, PP); + if (NTTP->isParameterPack()) + Out << "..."; + + if (NTTP->hasDefaultArgument()) { + P.Default.emplace(); + 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.Type); + OS << "template <"; + printParams(OS, + fetchTemplateParameters(TTPD->getTemplateParameters(), PP)); + OS << "> class"; // FIXME: TemplateTemplateParameter doesn't store the + // info on whether this param was a "typename" or + // "class". + if (!TTPD->getName().empty()) + P.Name = TTPD->getNameAsString(); + if (TTPD->hasDefaultArgument()) { + P.Default.emplace(); + llvm::raw_string_ostream Out(*P.Default); + TTPD->getDefaultArgument().getArgument().print(PP, Out); + } + } + TempParameters.push_back(std::move(P)); + } - if (isa(DC)) - return std::string("global namespace"); - if (const TypeDecl *TD = dyn_cast(DC)) - return typeDeclToString(TD); - else if (const NamespaceDecl *ND = dyn_cast(DC)) - return namedDeclQualifiedName(ND, "namespace"); - else if (const FunctionDecl *FD = dyn_cast(DC)) - return namedDeclQualifiedName(FD, "function"); + return TempParameters; +} - return None; +static llvm::Optional getTokenRange(SourceLocation Loc, + const ASTContext &Ctx) { + if (!Loc.isValid()) + return llvm::None; + SourceLocation End = Lexer::getLocForEndOfToken( + Loc, 0, Ctx.getSourceManager(), Ctx.getLangOpts()); + if (!End.isValid()) + return llvm::None; + return halfOpenToRange(Ctx.getSourceManager(), + CharSourceRange::getCharRange(Loc, End)); } /// Generate a \p Hover object given the declaration \p D. -static Hover getHoverContents(const Decl *D) { - Hover H; - llvm::Optional NamedScope = getScopeName(D); - - // Generate the "Declared in" section. - if (NamedScope) { - assert(!NamedScope->empty()); - - H.contents.value += "Declared in "; - H.contents.value += *NamedScope; - H.contents.value += "\n\n"; +static HoverInfo getHoverContents(const Decl *D) { + HoverInfo HI; + const ASTContext &Ctx = D->getASTContext(); + + HI.NamespaceScope = getNamespaceScope(D); + if (!HI.NamespaceScope->empty()) + HI.NamespaceScope->append("::"); + HI.LocalScope = getLocalScope(D); + if (!HI.LocalScope.empty()) + HI.LocalScope.append("::"); + + PrintingPolicy Policy = printingPolicyForDecls(Ctx.getPrintingPolicy()); + if (const NamedDecl *ND = llvm::dyn_cast(D)) { + HI.Documentation = getDeclComment(Ctx, *ND); + HI.Name = printName(Ctx, *ND); } - // We want to include the template in the Hover. - if (TemplateDecl *TD = D->getDescribedTemplate()) - D = TD; - - std::string DeclText; - llvm::raw_string_ostream OS(DeclText); - - PrintingPolicy Policy = - printingPolicyForDecls(D->getASTContext().getPrintingPolicy()); + HI.Kind = indexSymbolKindToSymbolKind(index::getSymbolInfo(D).Kind); - D->print(OS, Policy); + // Fill in template params. + 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; + } + } - OS.flush(); + // Fill in types and params. + if (const FunctionDecl *FD = D->getAsFunction()) { + HI.ReturnType.emplace(); + llvm::raw_string_ostream OS(*HI.ReturnType); + FD->getReturnType().print(OS, Policy); + + HI.Type.emplace(); + llvm::raw_string_ostream TypeOS(*HI.Type); + FD->getReturnType().print(TypeOS, Policy); + TypeOS << '('; + + HI.Parameters.emplace(); + for (const ParmVarDecl *PVD : FD->parameters()) { + if (HI.Parameters->size()) + TypeOS << ", "; + HI.Parameters->emplace_back(); + auto &P = HI.Parameters->back(); + if (!PVD->getType().isNull()) { + P.Type.emplace(); + llvm::raw_string_ostream OS(*P.Type); + PVD->getType().print(OS, Policy); + PVD->getType().print(TypeOS, Policy); + } else { + std::string Param; + llvm::raw_string_ostream OS(Param); + PVD->dump(OS); + OS.flush(); + elog("Got param with null type: {0}", Param); + } + if (!PVD->getName().empty()) + P.Name = PVD->getNameAsString(); + if (PVD->hasDefaultArg()) { + P.Default.emplace(); + llvm::raw_string_ostream Out(*P.Default); + PVD->getDefaultArg()->printPretty(Out, nullptr, Policy); + } + } + TypeOS << ')'; + // FIXME: handle variadics. + } else if (const auto *VD = dyn_cast(D)) { + // FIXME: Currently lambdas are also handled as ValueDecls, they should be + // more similar to functions. + HI.Type.emplace(); + llvm::raw_string_ostream OS(*HI.Type); + VD->getType().print(OS, Policy); + } - H.contents.value += DeclText; - return H; + HI.Definition = printDefinition(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); +static HoverInfo getHoverContents(QualType T, const Decl *D, + ASTContext &ASTCtx) { + HoverInfo HI; + llvm::raw_string_ostream OS(HI.Name); PrintingPolicy Policy = printingPolicyForDecls(ASTCtx.getPrintingPolicy()); T.print(OS, Policy); - OS.flush(); - H.contents.value += TypeText; - return H; + + if (D) + HI.Kind = indexSymbolKindToSymbolKind(index::getSymbolInfo(D).Kind); + 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.Name = Decl.Name; + HI.Kind = indexSymbolKindToSymbolKind( + index::getSymbolInfoForMacro(*Decl.Info).Kind); + // FIXME: Populate documentation + // FIXME: Pupulate parameters // Try to get the full definition, not just the name SourceLocation StartLoc = Decl.Info->getDefinitionLoc(); @@ -586,14 +738,12 @@ 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 = + ("#define " + Buffer.substr(StartOffset, EndOffset - StartOffset)) + .str(); } } - - Hover H; - H.contents.kind = MarkupKind::PlainText; - H.contents.value = "#define " + Definition; - return H; + return HI; } namespace { @@ -607,14 +757,11 @@ /// 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; @@ -626,8 +773,10 @@ return true; if (auto *AT = D->getType()->getContainedAutoType()) { - if (!AT->getDeducedType().isNull()) + if (!AT->getDeducedType().isNull()) { DeducedType = AT->getDeducedType(); + this->D = D; + } } return true; } @@ -655,13 +804,17 @@ const AutoType *AT = D->getReturnType()->getContainedAutoType(); if (AT && !AT->getDeducedType().isNull()) { DeducedType = AT->getDeducedType(); + this->D = D; } else if (auto DT = dyn_cast(D->getReturnType())) { // auto in a trailing return type just points to a DecltypeType and // getContainedAutoType does not unwrap it. - if (!DT->getUnderlyingType().isNull()) + if (!DT->getUnderlyingType().isNull()) { DeducedType = DT->getUnderlyingType(); + this->D = D; + } } else if (!D->getReturnType().isNull()) { DeducedType = D->getReturnType(); + this->D = D; } return true; } @@ -680,16 +833,19 @@ const DecltypeType *DT = dyn_cast(TL.getTypePtr()); while (DT && !DT->getUnderlyingType().isNull()) { DeducedType = DT->getUnderlyingType(); - DT = dyn_cast(DeducedType->getTypePtr()); + D = DT->getAsTagDecl(); + DT = dyn_cast(DeducedType.getTypePtr()); } return true; } + + QualType DeducedType; + const Decl *D = nullptr; }; } // namespace /// Retrieves the deduced type at a given location (auto, decltype). -llvm::Optional getDeducedType(ParsedAST &AST, - SourceLocation SourceLocationBeg) { +bool hasDeducedType(ParsedAST &AST, SourceLocation SourceLocationBeg) { Token Tok; auto &ASTCtx = AST.getASTContext(); // Only try to find a deduced type if the token is auto or decltype. @@ -697,18 +853,17 @@ Lexer::getRawToken(SourceLocationBeg, Tok, ASTCtx.getSourceManager(), ASTCtx.getLangOpts(), false) || !Tok.is(tok::raw_identifier)) { - return {}; + return false; } AST.getPreprocessor().LookUpIdentifierInfo(Tok); if (!(Tok.is(tok::kw_auto) || Tok.is(tok::kw_decltype))) - return {}; - - DeducedTypeVisitor V(SourceLocationBeg); - V.TraverseAST(AST.getASTContext()); - return V.getDeducedType(); + return false; + return true; } -llvm::Optional getHover(ParsedAST &AST, Position Pos) { +llvm::Optional getHover(ParsedAST &AST, Position Pos, + format::FormatStyle Style) { + llvm::Optional HI; const SourceManager &SourceMgr = AST.getASTContext().getSourceManager(); SourceLocation SourceLocationBeg = getBeginningOfIdentifier(AST, Pos, SourceMgr.getMainFileID()); @@ -716,16 +871,28 @@ auto Symbols = getSymbolAtPosition(AST, SourceLocationBeg); if (!Symbols.Macros.empty()) - return getHoverContents(Symbols.Macros[0], AST); - - if (!Symbols.Decls.empty()) - return getHoverContents(Symbols.Decls[0]); + HI = getHoverContents(Symbols.Macros[0], AST); + else if (!Symbols.Decls.empty()) + HI = getHoverContents(Symbols.Decls[0]); + else { + if (!hasDeducedType(AST, SourceLocationBeg)) + return None; + + DeducedTypeVisitor V(SourceLocationBeg); + V.TraverseAST(AST.getASTContext()); + if (V.DeducedType.isNull()) + return None; + HI = getHoverContents(V.DeducedType, V.D, AST.getASTContext()); + } - auto DeducedType = getDeducedType(AST, SourceLocationBeg); - if (DeducedType && !DeducedType->isNull()) - return getHoverContents(*DeducedType, AST.getASTContext()); + auto Replacements = format::reformat( + Style, HI->Definition, tooling::Range(0, HI->Definition.size())); + if (auto Formatted = + tooling::applyAllReplacements(HI->Definition, Replacements)) + HI->Definition = *Formatted; - return None; + HI->SymRange = getTokenRange(SourceLocationBeg, AST.getASTContext()); + return HI; } std::vector findReferences(ParsedAST &AST, Position Pos, @@ -748,7 +915,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 +1158,46 @@ return Result; } +MarkupContent HoverInfo::render() const { + MarkupContent Content; + Content.kind = MarkupKind::PlainText; + std::vector Output; + + if (NamespaceScope) { + llvm::raw_string_ostream Out(Content.value); + Out << "Declared in "; + // Drop trailing "::". + if (!LocalScope.empty()) + Out << *NamespaceScope << llvm::StringRef(LocalScope).drop_back(2); + else if (NamespaceScope->empty()) + Out << "global namespace"; + else + Out << llvm::StringRef(*NamespaceScope).drop_back(2); + Out << "\n\n"; + } + + if (!Definition.empty()) { + Output.push_back(Definition); + } else { + // Builtin types + Output.push_back(Name); + } + Content.value += llvm::join(Output, " "); + return Content; +} + +llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, + const HoverInfo::Param &P) { + std::vector Output; + if (P.Type) + Output.push_back(*P.Type); + if (P.Name) + Output.push_back(*P.Name); + OS << llvm::join(Output, " "); + if (P.Default) + OS << " = " << *P.Default; + return OS; +} + } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/test/hover.test b/clang-tools-extra/clangd/test/hover.test --- a/clang-tools-extra/clangd/test/hover.test +++ b/clang-tools-extra/clangd/test/hover.test @@ -10,6 +10,16 @@ # CHECK-NEXT: "contents": { # CHECK-NEXT: "kind": "plaintext", # CHECK-NEXT: "value": "Declared in global namespace\n\nvoid foo()" +# CHECK-NEXT: }, +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 28, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 25, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: } # CHECK-NEXT: } # CHECK-NEXT: } # CHECK-NEXT:} @@ -19,6 +29,29 @@ # CHECK-NEXT: "jsonrpc": "2.0", # CHECK-NEXT: "result": null --- +{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///main2.cpp","languageId":"cpp","version":1,"text":"enum foo{}; int main() { foo f; }\n"}}} +--- +{"jsonrpc":"2.0","id":1,"method":"textDocument/hover","params":{"textDocument":{"uri":"test:///main2.cpp"},"position":{"line":0,"character":27}}} +# CHECK: "id": 1, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": { +# CHECK-NEXT: "contents": { +# CHECK-NEXT: "kind": "plaintext", +# CHECK-NEXT: "value": "Declared in global namespace\n\nenum foo {}" +# CHECK-NEXT: }, +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 28, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 25, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT:} +--- {"jsonrpc":"2.0","id":3,"method":"shutdown"} --- {"jsonrpc":"2.0","method":"exit"} diff --git a/clang-tools-extra/clangd/unittests/TestTU.cpp b/clang-tools-extra/clangd/unittests/TestTU.cpp --- a/clang-tools-extra/clangd/unittests/TestTU.cpp +++ b/clang-tools-extra/clangd/unittests/TestTU.cpp @@ -59,8 +59,7 @@ /*OldPreamble=*/nullptr, /*OldCompileCommand=*/Inputs.CompileCommand, Inputs, /*StoreInMemory=*/true, /*PreambleCallback=*/nullptr); - auto AST = buildAST(FullFilename, createInvocationFromCommandLine(Cmd), - Inputs, Preamble); + auto AST = buildAST(FullFilename, std::move(CI), Inputs, Preamble); if (!AST.hasValue()) { ADD_FAILURE() << "Failed to build code:\n" << Code; llvm_unreachable("Failed to build TestTU!"); 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 @@ -9,6 +9,7 @@ #include "ClangdUnit.h" #include "Compiler.h" #include "Matchers.h" +#include "Protocol.h" #include "SyncAPI.h" #include "TestFS.h" #include "TestTU.h" @@ -16,6 +17,7 @@ #include "index/FileIndex.h" #include "index/SymbolCollector.h" #include "clang/Index/IndexingAction.h" +#include "llvm/ADT/None.h" #include "llvm/Support/Path.h" #include "llvm/Support/ScopedPrinter.h" #include "gmock/gmock.h" @@ -558,6 +560,306 @@ HeaderNotInPreambleAnnotations.range()))); } +TEST(Hover, Structured) { + struct { + const char *const Code; + const std::function ExpectedBuilder; + } Cases[] = { + // Global scope. + {R"cpp( + // Best foo ever. + void [[fo^o]]() {} + )cpp", + [](HoverInfo &HI) { + HI.NamespaceScope = ""; + HI.Name = "foo"; + HI.Kind = SymbolKind::Function; + HI.Documentation = "Best foo ever."; + HI.Definition = "void foo()"; + HI.ReturnType = "void"; + HI.Type = "void()"; + HI.Parameters.emplace(); + }}, + // Inside namespace + {R"cpp( + namespace ns1 { namespace ns2 { + /// Best foo ever. + void [[fo^o]]() {} + }} + )cpp", + [](HoverInfo &HI) { + HI.NamespaceScope = "ns1::ns2::"; + HI.Name = "foo"; + HI.Kind = SymbolKind::Function; + HI.Documentation = "Best foo ever."; + HI.Definition = "void foo()"; + HI.ReturnType = "void"; + HI.Type = "void()"; + HI.Parameters.emplace(); + }}, + // Field + {R"cpp( + namespace ns1 { namespace ns2 { + struct Foo { + int [[b^ar]]; + }; + }} + )cpp", + [](HoverInfo &HI) { + HI.NamespaceScope = "ns1::ns2::"; + HI.LocalScope = "Foo::"; + HI.Name = "bar"; + HI.Kind = SymbolKind::Field; + HI.Definition = "int bar"; + HI.Type = "int"; + }}, + // Local to class method. + {R"cpp( + namespace ns1 { namespace ns2 { + struct Foo { + void foo() { + int [[b^ar]]; + } + }; + }} + )cpp", + [](HoverInfo &HI) { + HI.NamespaceScope = "ns1::ns2::"; + HI.LocalScope = "Foo::foo::"; + HI.Name = "bar"; + HI.Kind = SymbolKind::Variable; + HI.Definition = "int bar"; + HI.Type = "int"; + }}, + // Anon namespace and local scope. + {R"cpp( + namespace ns1 { namespace { + struct { + int [[b^ar]]; + } T; + }} + )cpp", + [](HoverInfo &HI) { + HI.NamespaceScope = "ns1::(anonymous)::"; + HI.LocalScope = "(anonymous struct)::"; + HI.Name = "bar"; + HI.Kind = SymbolKind::Field; + HI.Definition = "int bar"; + HI.Type = "int"; + }}, + // Variable with template type + {R"cpp( + template class Foo {}; + Foo [[fo^o]]; + )cpp", + [](HoverInfo &HI) { + HI.NamespaceScope = ""; + HI.Name = "foo"; + HI.Kind = SymbolKind::Variable; + HI.Definition = "Foo foo"; + HI.Type = "Foo"; + }}, + // Implicit template instantiation + {R"cpp( + template class vector{}; + [[vec^tor]] foo; + )cpp", + [](HoverInfo &HI) { + HI.NamespaceScope = ""; + HI.Name = "vector"; + HI.Kind = SymbolKind::Class; + HI.Definition = "template class vector {}"; + HI.TemplateParameters = { + {std::string("typename"), std::string("T"), llvm::None}, + }; + }}, + // Class template + {R"cpp( + template class C, + typename = char, + int = 0, + bool Q = false, + class... Ts> class Foo {}; + template class T> + [[F^oo]] foo; + )cpp", + [](HoverInfo &HI) { + HI.NamespaceScope = ""; + HI.Name = "Foo"; + HI.Kind = SymbolKind::Class; + HI.Definition = + R"cpp(template