diff --git a/clang-tools-extra/clangd/AST.h b/clang-tools-extra/clangd/AST.h --- a/clang-tools-extra/clangd/AST.h +++ b/clang-tools-extra/clangd/AST.h @@ -116,6 +116,11 @@ // (i.e. vector rather than vector. QualType declaredType(const TypeDecl *D); +/// Retrieves the deduced type at a given location (auto, decltype). +/// Retuns None unless Loc starts an auto/decltype token. +/// It will return the underlying type. +llvm::Optional getDeducedType(ASTContext &, SourceLocation Loc); + } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/AST.cpp b/clang-tools-extra/clangd/AST.cpp --- a/clang-tools-extra/clangd/AST.cpp +++ b/clang-tools-extra/clangd/AST.cpp @@ -15,11 +15,13 @@ #include "clang/AST/DeclarationName.h" #include "clang/AST/NestedNameSpecifier.h" #include "clang/AST/PrettyPrinter.h" +#include "clang/AST/RecursiveASTVisitor.h" #include "clang/AST/TemplateBase.h" #include "clang/Basic/SourceLocation.h" #include "clang/Basic/SourceManager.h" #include "clang/Basic/Specifiers.h" #include "clang/Index/USRGeneration.h" +#include "clang/Lex/Lexer.h" #include "llvm/ADT/Optional.h" #include "llvm/Support/Casting.h" #include "llvm/Support/ScopedPrinter.h" @@ -253,5 +255,114 @@ return D->getASTContext().getTypeDeclType(D); } +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; + +public: + DeducedTypeVisitor(SourceLocation SearchedLocation) + : SearchedLocation(SearchedLocation) {} + + // Handle auto initializers: + //- auto i = 1; + //- decltype(auto) i = 1; + //- auto& i = 1; + //- auto* i = &a; + bool VisitDeclaratorDecl(DeclaratorDecl *D) { + if (!D->getTypeSourceInfo() || + D->getTypeSourceInfo()->getTypeLoc().getBeginLoc() != SearchedLocation) + return true; + + if (auto *AT = D->getType()->getContainedAutoType()) { + if (!AT->getDeducedType().isNull()) + DeducedType = AT->getDeducedType(); + } + return true; + } + + // Handle auto return types: + //- auto foo() {} + //- auto& foo() {} + //- auto foo() -> int {} + //- 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; + + const AutoType *AT = D->getReturnType()->getContainedAutoType(); + if (AT && !AT->getDeducedType().isNull()) { + DeducedType = AT->getDeducedType(); + } 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()) + DeducedType = DT->getUnderlyingType(); + } else if (!D->getReturnType().isNull()) { + DeducedType = D->getReturnType(); + } + 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; + } + + QualType DeducedType; +}; +} // namespace + +llvm::Optional getDeducedType(ASTContext &ASTCtx, + SourceLocation Loc) { + Token Tok; + // Only try to find a deduced type if the token is auto or decltype. + if (!Loc.isValid() || + Lexer::getRawToken(Loc, Tok, ASTCtx.getSourceManager(), + ASTCtx.getLangOpts(), false) || + !Tok.is(tok::raw_identifier) || + !(Tok.getRawIdentifier() == "auto" || + Tok.getRawIdentifier() == "decltype")) { + return {}; + } + DeducedTypeVisitor V(Loc); + V.TraverseAST(ASTCtx); + if (V.DeducedType.isNull()) + return llvm::None; + return V.DeducedType; +} + } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/CMakeLists.txt b/clang-tools-extra/clangd/CMakeLists.txt --- a/clang-tools-extra/clangd/CMakeLists.txt +++ b/clang-tools-extra/clangd/CMakeLists.txt @@ -57,6 +57,7 @@ GlobalCompilationDatabase.cpp Headers.cpp HeaderSourceSwitch.cpp + Hover.cpp IncludeFixer.cpp JSONTransport.cpp Logger.cpp 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 @@ -16,6 +16,7 @@ #include "FormattedString.h" #include "Function.h" #include "GlobalCompilationDatabase.h" +#include "Hover.h" #include "Protocol.h" #include "SemanticHighlighting.h" #include "TUScheduler.h" diff --git a/clang-tools-extra/clangd/Hover.h b/clang-tools-extra/clangd/Hover.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clangd/Hover.h @@ -0,0 +1,93 @@ +//===--- Hover.h - Information about code at the cursor location -*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_HOVER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_HOVER_H + +#include "FormattedString.h" +#include "ParsedAST.h" +#include "Protocol.h" + +namespace clang { +namespace clangd { + +/// 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; + /// Contains the evaluated value of the symbol if available. + llvm::Optional Value; + + /// Produce a user-readable information. + FormattedString present() 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, + format::FormatStyle Style, + const SymbolIndex *Index); + +} // namespace clangd +} // namespace clang + +#endif diff --git a/clang-tools-extra/clangd/Hover.cpp b/clang-tools-extra/clangd/Hover.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clangd/Hover.cpp @@ -0,0 +1,443 @@ +//===--- Hover.cpp - Information about code at the cursor location --------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "Hover.h" + +#include "AST.h" +#include "CodeCompletionStrings.h" +#include "FindTarget.h" +#include "Logger.h" +#include "Selection.h" +#include "SourceCode.h" +#include "index/SymbolCollector.h" + +#include "clang/AST/ASTTypeTraits.h" +#include "clang/AST/DeclTemplate.h" +#include "clang/AST/PrettyPrinter.h" + +namespace clang { +namespace clangd { +namespace { + +PrintingPolicy printingPolicyForDecls(PrintingPolicy Base) { + PrintingPolicy Policy(Base); + + Policy.AnonymousTagLocations = false; + Policy.TerseOutput = true; + Policy.PolishForDeclaration = true; + Policy.ConstantsAsWritten = true; + Policy.SuppressTagKeyword = false; + + return Policy; +} + +/// 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. +std::string getLocalScope(const Decl *D) { + std::vector Scopes; + const DeclContext *DC = D->getDeclContext(); + auto GetName = [](const TypeDecl *D) { + if (!D->getDeclName().isEmpty()) { + PrintingPolicy Policy = D->getASTContext().getPrintingPolicy(); + Policy.SuppressScope = true; + return declaredType(D).getAsString(Policy); + } + 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(); + } + + return llvm::join(llvm::reverse(Scopes), "::"); +} + +/// Returns the human-readable representation for namespace containing the +/// declaration \p D. Returns empty if it is contained global namespace. +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 ""; +} + +std::string printDefinition(const Decl *D) { + std::string Definition; + llvm::raw_string_ostream OS(Definition); + PrintingPolicy Policy = + printingPolicyForDecls(D->getASTContext().getPrintingPolicy()); + Policy.IncludeTagDefinition = false; + Policy.SuppressTemplateArgsInCXXConstructors = true; + D->print(OS, Policy); + OS.flush(); + return Definition; +} + +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); + } +} + +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)); + } + + return TempParameters; +} + +const FunctionDecl *getUnderlyingFunction(const Decl *D) { + // Extract lambda from variables. + if (const VarDecl *VD = llvm::dyn_cast(D)) { + auto QT = VD->getType(); + if (!QT.isNull()) { + while (!QT->getPointeeType().isNull()) + QT = QT->getPointeeType(); + + if (const auto *CD = QT->getAsCXXRecordDecl()) + return CD->getLambdaCallOperator(); + } + } + + // Non-lambda functions. + return D->getAsFunction(); +} + +// Look up information about D from the index, and add it to Hover. +void enhanceFromIndex(HoverInfo &Hover, const Decl *D, + const SymbolIndex *Index) { + if (!Index || !llvm::isa(D)) + return; + const NamedDecl &ND = *cast(D); + // We only add documentation, so don't bother if we already have some. + if (!Hover.Documentation.empty()) + return; + // Skip querying for non-indexable symbols, there's no point. + // We're searching for symbols that might be indexed outside this main file. + if (!SymbolCollector::shouldCollectSymbol(ND, ND.getASTContext(), + SymbolCollector::Options(), + /*IsMainFileOnly=*/false)) + return; + auto ID = getSymbolID(&ND); + if (!ID) + return; + LookupRequest Req; + Req.IDs.insert(*ID); + Index->lookup( + Req, [&](const Symbol &S) { Hover.Documentation = S.Documentation; }); +} + +// Populates Type, ReturnType, and Parameters for function-like decls. +void fillFunctionTypeAndParams(HoverInfo &HI, const Decl *D, + const FunctionDecl *FD, + const PrintingPolicy &Policy) { + HI.Parameters.emplace(); + for (const ParmVarDecl *PVD : FD->parameters()) { + 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); + } 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); + } + } + + if (const auto* CCD = llvm::dyn_cast(FD)) { + // Constructor's "return type" is the class type. + HI.ReturnType = declaredType(CCD->getParent()).getAsString(Policy); + // Don't provide any type for the constructor itself. + } else if (const auto* CDD = llvm::dyn_cast(FD)){ + HI.ReturnType = "void"; + } else { + HI.ReturnType = FD->getReturnType().getAsString(Policy); + + QualType FunctionType = FD->getType(); + if (const VarDecl *VD = llvm::dyn_cast(D)) // Lambdas + FunctionType = VD->getType().getDesugaredType(D->getASTContext()); + HI.Type = FunctionType.getAsString(Policy); + } + // FIXME: handle variadics. +} + +/// Generate a \p Hover object given the declaration \p D. +HoverInfo getHoverContents(const Decl *D, const SymbolIndex *Index) { + 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); + } + + HI.Kind = indexSymbolKindToSymbolKind(index::getSymbolInfo(D).Kind); + + // 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; + } + } + + // Fill in types and params. + if (const FunctionDecl *FD = getUnderlyingFunction(D)) { + fillFunctionTypeAndParams(HI, D, FD, Policy); + } else if (const auto *VD = dyn_cast(D)) { + HI.Type.emplace(); + llvm::raw_string_ostream OS(*HI.Type); + VD->getType().print(OS, Policy); + } + + // Fill in value with evaluated initializer if possible. + // FIXME(kadircet): Also set Value field for expressions like "sizeof" and + // function calls. + if (const auto *Var = dyn_cast(D)) { + if (const Expr *Init = Var->getInit()) { + Expr::EvalResult Result; + if (!Init->isValueDependent() && Init->EvaluateAsRValue(Result, Ctx)) { + HI.Value.emplace(); + llvm::raw_string_ostream ValueOS(*HI.Value); + Result.Val.printPretty(ValueOS, const_cast(Ctx), + Init->getType()); + } + } + } else if (const auto *ECD = dyn_cast(D)) { + // Dependent enums (e.g. nested in template classes) don't have values yet. + if (!ECD->getType()->isDependentType()) + HI.Value = ECD->getInitVal().toString(10); + } + + HI.Definition = printDefinition(D); + enhanceFromIndex(HI, D, Index); + return HI; +} + +/// Generate a \p Hover object given the type \p T. +HoverInfo getHoverContents(QualType T, const Decl *D, ASTContext &ASTCtx, + const SymbolIndex *Index) { + HoverInfo HI; + llvm::raw_string_ostream OS(HI.Name); + PrintingPolicy Policy = printingPolicyForDecls(ASTCtx.getPrintingPolicy()); + T.print(OS, Policy); + OS.flush(); + + if (D) { + HI.Kind = indexSymbolKindToSymbolKind(index::getSymbolInfo(D).Kind); + enhanceFromIndex(HI, D, Index); + } + return HI; +} + +/// Generate a \p Hover object given the macro \p MacroDecl. +HoverInfo getHoverContents(const DefinedMacro &Macro, ParsedAST &AST) { + HoverInfo HI; + SourceManager &SM = AST.getSourceManager(); + HI.Name = Macro.Name; + HI.Kind = indexSymbolKindToSymbolKind( + index::getSymbolInfoForMacro(*Macro.Info).Kind); + // FIXME: Populate documentation + // FIXME: Pupulate parameters + + // Try to get the full definition, not just the name + SourceLocation StartLoc = Macro.Info->getDefinitionLoc(); + SourceLocation EndLoc = Macro.Info->getDefinitionEndLoc(); + if (EndLoc.isValid()) { + EndLoc = Lexer::getLocForEndOfToken(EndLoc, 0, SM, + AST.getASTContext().getLangOpts()); + bool Invalid; + StringRef Buffer = SM.getBufferData(SM.getFileID(StartLoc), &Invalid); + if (!Invalid) { + unsigned StartOffset = SM.getFileOffset(StartLoc); + unsigned EndOffset = SM.getFileOffset(EndLoc); + if (EndOffset <= Buffer.size() && StartOffset < EndOffset) + HI.Definition = + ("#define " + Buffer.substr(StartOffset, EndOffset - StartOffset)) + .str(); + } + } + return HI; +} + +} // namespace + +llvm::Optional getHover(ParsedAST &AST, Position Pos, + format::FormatStyle Style, + const SymbolIndex *Index) { + const SourceManager &SM = AST.getSourceManager(); + llvm::Optional HI; + SourceLocation SourceLocationBeg = SM.getMacroArgExpandedLocation( + getBeginningOfIdentifier(Pos, SM, AST.getASTContext().getLangOpts())); + + if (auto Deduced = getDeducedType(AST.getASTContext(), SourceLocationBeg)) { + // Find the corresponding decl to populate kind and fetch documentation. + DeclRelationSet Rel = DeclRelation::TemplatePattern | DeclRelation::Alias; + auto Decls = + targetDecl(ast_type_traits::DynTypedNode::create(*Deduced), Rel); + HI = getHoverContents(*Deduced, Decls.empty() ? nullptr : Decls.front(), + AST.getASTContext(), Index); + } else if (auto M = locateMacroAt(SourceLocationBeg, AST.getPreprocessor())) { + HI = getHoverContents(*M, AST); + } else { + auto Offset = positionToOffset(SM.getBufferData(SM.getMainFileID()), Pos); + if (!Offset) { + llvm::consumeError(Offset.takeError()); + return llvm::None; + } + SelectionTree Selection(AST.getASTContext(), AST.getTokens(), *Offset); + std::vector Result; + if (const SelectionTree::Node *N = Selection.commonAncestor()) { + DeclRelationSet Rel = DeclRelation::TemplatePattern | DeclRelation::Alias; + auto Decls = targetDecl(N->ASTNode, Rel); + if (!Decls.empty()) + HI = getHoverContents(Decls.front(), Index); + } + } + + if (!HI) + return llvm::None; + + auto Replacements = format::reformat( + Style, HI->Definition, tooling::Range(0, HI->Definition.size())); + if (auto Formatted = + tooling::applyAllReplacements(HI->Definition, Replacements)) + HI->Definition = *Formatted; + + HI->SymRange = + getTokenRange(AST.getASTContext().getSourceManager(), + AST.getASTContext().getLangOpts(), SourceLocationBeg); + return HI; +} + +FormattedString HoverInfo::present() const { + FormattedString Output; + if (NamespaceScope) { + Output.appendText("Declared in"); + // Drop trailing "::". + if (!LocalScope.empty()) + Output.appendInlineCode(llvm::StringRef(LocalScope).drop_back(2)); + else if (NamespaceScope->empty()) + Output.appendInlineCode("global namespace"); + else + Output.appendInlineCode(llvm::StringRef(*NamespaceScope).drop_back(2)); + } + + if (!Definition.empty()) { + Output.appendCodeBlock(Definition); + } else { + // Builtin types + Output.appendCodeBlock(Name); + } + + if (!Documentation.empty()) + Output.appendText(Documentation); + return Output; +} + +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/XRefs.h b/clang-tools-extra/clangd/XRefs.h --- a/clang-tools-extra/clangd/XRefs.h +++ b/clang-tools-extra/clangd/XRefs.h @@ -53,77 +53,6 @@ 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; - /// Contains the evaluated value of the symbol if available. - llvm::Optional Value; - - /// Produce a user-readable information. - FormattedString present() 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, - format::FormatStyle Style, - const SymbolIndex *Index); - /// Returns reference locations of the symbol at a specified \p Pos. /// \p Limit limits the number of results returned (0 means no limit). std::vector findReferences(ParsedAST &AST, Position Pos, @@ -148,16 +77,6 @@ TypeHierarchyDirection Direction, const SymbolIndex *Index); -/// Retrieves the deduced type at a given location (auto, decltype). -/// Retuns None unless SourceLocationBeg starts an auto/decltype token. -/// It will return the underlying type. -llvm::Optional getDeducedType(ParsedAST &AST, - SourceLocation SourceLocationBeg); - -/// Check if there is a deduced type at a given location (auto, decltype). -/// SourceLocationBeg must point to the first character of the token -bool hasDeducedType(ParsedAST &AST, SourceLocation SourceLocationBeg); - /// Returns all decls that are referenced in the \p FD except local symbols. llvm::DenseSet getNonLocalDeclRefs(ParsedAST &AST, const FunctionDecl *FD); 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 @@ -10,7 +10,6 @@ #include "CodeCompletionStrings.h" #include "FindSymbols.h" #include "FindTarget.h" -#include "FormattedString.h" #include "Logger.h" #include "ParsedAST.h" #include "Protocol.h" @@ -20,15 +19,12 @@ #include "index/Index.h" #include "index/Merge.h" #include "index/Relation.h" -#include "index/SymbolCollector.h" #include "index/SymbolLocation.h" #include "clang/AST/ASTContext.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclTemplate.h" #include "clang/AST/ExprCXX.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" @@ -44,7 +40,6 @@ #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" @@ -396,500 +391,6 @@ return Result; } -static PrintingPolicy printingPolicyForDecls(PrintingPolicy Base) { - PrintingPolicy Policy(Base); - - Policy.AnonymousTagLocations = false; - Policy.TerseOutput = true; - Policy.PolishForDeclaration = true; - Policy.ConstantsAsWritten = true; - Policy.SuppressTagKeyword = false; - - return Policy; -} - -/// 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 TypeDecl *D) { - if (!D->getDeclName().isEmpty()) { - PrintingPolicy Policy = D->getASTContext().getPrintingPolicy(); - Policy.SuppressScope = true; - return declaredType(D).getAsString(Policy); - } - 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(); - } - - return llvm::join(llvm::reverse(Scopes), "::"); -} - -/// 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 ""; -} - -static std::string printDefinition(const Decl *D) { - std::string Definition; - llvm::raw_string_ostream OS(Definition); - PrintingPolicy Policy = - printingPolicyForDecls(D->getASTContext().getPrintingPolicy()); - Policy.IncludeTagDefinition = false; - Policy.SuppressTemplateArgsInCXXConstructors = true; - D->print(OS, Policy); - OS.flush(); - return Definition; -} - -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); - } -} - -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)); - } - - return TempParameters; -} - -static const FunctionDecl *getUnderlyingFunction(const Decl *D) { - // Extract lambda from variables. - if (const VarDecl *VD = llvm::dyn_cast(D)) { - auto QT = VD->getType(); - if (!QT.isNull()) { - while (!QT->getPointeeType().isNull()) - QT = QT->getPointeeType(); - - if (const auto *CD = QT->getAsCXXRecordDecl()) - return CD->getLambdaCallOperator(); - } - } - - // Non-lambda functions. - return D->getAsFunction(); -} - -// Look up information about D from the index, and add it to Hover. -static void enhanceFromIndex(HoverInfo &Hover, const Decl *D, - const SymbolIndex *Index) { - if (!Index || !llvm::isa(D)) - return; - const NamedDecl &ND = *cast(D); - // We only add documentation, so don't bother if we already have some. - if (!Hover.Documentation.empty()) - return; - // Skip querying for non-indexable symbols, there's no point. - // We're searching for symbols that might be indexed outside this main file. - if (!SymbolCollector::shouldCollectSymbol(ND, ND.getASTContext(), - SymbolCollector::Options(), - /*IsMainFileOnly=*/false)) - return; - auto ID = getSymbolID(&ND); - if (!ID) - return; - LookupRequest Req; - Req.IDs.insert(*ID); - Index->lookup( - Req, [&](const Symbol &S) { Hover.Documentation = S.Documentation; }); -} - -// Populates Type, ReturnType, and Parameters for function-like decls. -static void fillFunctionTypeAndParams(HoverInfo &HI, const Decl *D, - const FunctionDecl *FD, - const PrintingPolicy &Policy) { - HI.Parameters.emplace(); - for (const ParmVarDecl *PVD : FD->parameters()) { - 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); - } 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); - } - } - - if (const auto* CCD = llvm::dyn_cast(FD)) { - // Constructor's "return type" is the class type. - HI.ReturnType = declaredType(CCD->getParent()).getAsString(Policy); - // Don't provide any type for the constructor itself. - } else if (const auto* CDD = llvm::dyn_cast(FD)){ - HI.ReturnType = "void"; - } else { - HI.ReturnType = FD->getReturnType().getAsString(Policy); - - QualType FunctionType = FD->getType(); - if (const VarDecl *VD = llvm::dyn_cast(D)) // Lambdas - FunctionType = VD->getType().getDesugaredType(D->getASTContext()); - HI.Type = FunctionType.getAsString(Policy); - } - // FIXME: handle variadics. -} - -/// Generate a \p Hover object given the declaration \p D. -static HoverInfo getHoverContents(const Decl *D, const SymbolIndex *Index) { - 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); - } - - HI.Kind = indexSymbolKindToSymbolKind(index::getSymbolInfo(D).Kind); - - // 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; - } - } - - // Fill in types and params. - if (const FunctionDecl *FD = getUnderlyingFunction(D)) { - fillFunctionTypeAndParams(HI, D, FD, Policy); - } else if (const auto *VD = dyn_cast(D)) { - HI.Type.emplace(); - llvm::raw_string_ostream OS(*HI.Type); - VD->getType().print(OS, Policy); - } - - // Fill in value with evaluated initializer if possible. - // FIXME(kadircet): Also set Value field for expressions like "sizeof" and - // function calls. - if (const auto *Var = dyn_cast(D)) { - if (const Expr *Init = Var->getInit()) { - Expr::EvalResult Result; - if (!Init->isValueDependent() && Init->EvaluateAsRValue(Result, Ctx)) { - HI.Value.emplace(); - llvm::raw_string_ostream ValueOS(*HI.Value); - Result.Val.printPretty(ValueOS, const_cast(Ctx), - Init->getType()); - } - } - } else if (const auto *ECD = dyn_cast(D)) { - // Dependent enums (e.g. nested in template classes) don't have values yet. - if (!ECD->getType()->isDependentType()) - HI.Value = ECD->getInitVal().toString(10); - } - - HI.Definition = printDefinition(D); - enhanceFromIndex(HI, D, Index); - return HI; -} - -/// Generate a \p Hover object given the type \p T. -static HoverInfo getHoverContents(QualType T, const Decl *D, ASTContext &ASTCtx, - const SymbolIndex *Index) { - HoverInfo HI; - llvm::raw_string_ostream OS(HI.Name); - PrintingPolicy Policy = printingPolicyForDecls(ASTCtx.getPrintingPolicy()); - T.print(OS, Policy); - OS.flush(); - - if (D) { - HI.Kind = indexSymbolKindToSymbolKind(index::getSymbolInfo(D).Kind); - enhanceFromIndex(HI, D, Index); - } - return HI; -} - -/// Generate a \p Hover object given the macro \p MacroDecl. -static HoverInfo getHoverContents(const DefinedMacro &Macro, ParsedAST &AST) { - HoverInfo HI; - SourceManager &SM = AST.getSourceManager(); - HI.Name = Macro.Name; - HI.Kind = indexSymbolKindToSymbolKind( - index::getSymbolInfoForMacro(*Macro.Info).Kind); - // FIXME: Populate documentation - // FIXME: Pupulate parameters - - // Try to get the full definition, not just the name - SourceLocation StartLoc = Macro.Info->getDefinitionLoc(); - SourceLocation EndLoc = Macro.Info->getDefinitionEndLoc(); - if (EndLoc.isValid()) { - EndLoc = Lexer::getLocForEndOfToken(EndLoc, 0, SM, - AST.getASTContext().getLangOpts()); - bool Invalid; - StringRef Buffer = SM.getBufferData(SM.getFileID(StartLoc), &Invalid); - if (!Invalid) { - unsigned StartOffset = SM.getFileOffset(StartLoc); - unsigned EndOffset = SM.getFileOffset(EndLoc); - if (EndOffset <= Buffer.size() && StartOffset < EndOffset) - HI.Definition = - ("#define " + Buffer.substr(StartOffset, EndOffset - StartOffset)) - .str(); - } - } - return HI; -} - -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; - -public: - DeducedTypeVisitor(SourceLocation SearchedLocation) - : SearchedLocation(SearchedLocation) {} - - // Handle auto initializers: - //- auto i = 1; - //- decltype(auto) i = 1; - //- auto& i = 1; - //- auto* i = &a; - bool VisitDeclaratorDecl(DeclaratorDecl *D) { - if (!D->getTypeSourceInfo() || - D->getTypeSourceInfo()->getTypeLoc().getBeginLoc() != SearchedLocation) - return true; - - if (auto *AT = D->getType()->getContainedAutoType()) { - if (!AT->getDeducedType().isNull()) { - DeducedType = AT->getDeducedType(); - this->D = D; - } - } - return true; - } - - // Handle auto return types: - //- auto foo() {} - //- auto& foo() {} - //- auto foo() -> int {} - //- 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; - - 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()) { - DeducedType = DT->getUnderlyingType(); - this->D = D; - } - } else if (!D->getReturnType().isNull()) { - DeducedType = D->getReturnType(); - this->D = D; - } - 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(); - 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). -/// SourceLocationBeg must point to the first character of the token -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); - V.TraverseAST(AST.getASTContext()); - return V.DeducedType; -} - -/// Retrieves the deduced type at a given location (auto, decltype). -bool hasDeducedType(ParsedAST &AST, SourceLocation SourceLocationBeg) { - return (bool)getDeducedType(AST, SourceLocationBeg); -} - -llvm::Optional getHover(ParsedAST &AST, Position Pos, - format::FormatStyle Style, - const SymbolIndex *Index) { - const SourceManager &SM = AST.getSourceManager(); - llvm::Optional HI; - SourceLocation SourceLocationBeg = SM.getMacroArgExpandedLocation( - getBeginningOfIdentifier(Pos, SM, AST.getASTContext().getLangOpts())); - - if (hasDeducedType(AST, SourceLocationBeg)) { - DeducedTypeVisitor V(SourceLocationBeg); - V.TraverseAST(AST.getASTContext()); - if (!V.DeducedType.isNull()) - HI = getHoverContents(V.DeducedType, V.D, AST.getASTContext(), Index); - } - - if (!HI) { - if (auto M = locateMacroAt(SourceLocationBeg, AST.getPreprocessor())) { - HI = getHoverContents(*M, AST); - } else { - DeclRelationSet Relations = - DeclRelation::TemplatePattern | DeclRelation::Alias; - auto Decls = getDeclAtPosition(AST, SourceLocationBeg, Relations); - if (!Decls.empty()) - HI = getHoverContents(Decls.front(), Index); - } - } - - if (!HI) - return llvm::None; - - auto Replacements = format::reformat( - Style, HI->Definition, tooling::Range(0, HI->Definition.size())); - if (auto Formatted = - tooling::applyAllReplacements(HI->Definition, Replacements)) - HI->Definition = *Formatted; - - HI->SymRange = - getTokenRange(AST.getASTContext().getSourceManager(), - AST.getASTContext().getLangOpts(), SourceLocationBeg); - return HI; -} - std::vector findReferences(ParsedAST &AST, Position Pos, uint32_t Limit, const SymbolIndex *Index) { if (!Limit) @@ -1250,44 +751,6 @@ } } -FormattedString HoverInfo::present() const { - FormattedString Output; - if (NamespaceScope) { - Output.appendText("Declared in"); - // Drop trailing "::". - if (!LocalScope.empty()) - Output.appendInlineCode(llvm::StringRef(LocalScope).drop_back(2)); - else if (NamespaceScope->empty()) - Output.appendInlineCode("global namespace"); - else - Output.appendInlineCode(llvm::StringRef(*NamespaceScope).drop_back(2)); - } - - if (!Definition.empty()) { - Output.appendCodeBlock(Definition); - } else { - // Builtin types - Output.appendCodeBlock(Name); - } - - if (!Documentation.empty()) - Output.appendText(Documentation); - return Output; -} - -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; -} - llvm::DenseSet getNonLocalDeclRefs(ParsedAST &AST, const FunctionDecl *FD) { if (!FD->hasBody()) diff --git a/clang-tools-extra/clangd/refactor/tweaks/ExpandAutoType.cpp b/clang-tools-extra/clangd/refactor/tweaks/ExpandAutoType.cpp --- a/clang-tools-extra/clangd/refactor/tweaks/ExpandAutoType.cpp +++ b/clang-tools-extra/clangd/refactor/tweaks/ExpandAutoType.cpp @@ -74,12 +74,11 @@ auto& SrcMgr = Inputs.AST.getASTContext().getSourceManager(); llvm::Optional DeducedType = - getDeducedType(Inputs.AST, CachedLocation->getBeginLoc()); + getDeducedType(Inputs.AST.getASTContext(), CachedLocation->getBeginLoc()); // if we can't resolve the type, return an error message - if (DeducedType == llvm::None || DeducedType->isNull()) { + if (DeducedType == llvm::None) return createErrorMessage("Could not deduce type for 'auto' type", Inputs); - } // if it's a lambda expression, return an error message if (isa(*DeducedType) && diff --git a/clang-tools-extra/clangd/unittests/ASTTests.cpp b/clang-tools-extra/clangd/unittests/ASTTests.cpp --- a/clang-tools-extra/clangd/unittests/ASTTests.cpp +++ b/clang-tools-extra/clangd/unittests/ASTTests.cpp @@ -7,13 +7,17 @@ //===----------------------------------------------------------------------===// #include "AST.h" + +#include "Annotations.h" +#include "TestTU.h" +#include "clang/Basic/SourceManager.h" #include "gtest/gtest.h" namespace clang { namespace clangd { namespace { -TEST(ExpandAutoType, ShortenNamespace) { +TEST(ShortenNamespace, All) { ASSERT_EQ("TestClass", shortenNamespace("TestClass", "")); ASSERT_EQ("TestClass", shortenNamespace( @@ -36,6 +40,30 @@ "testns1::TestClass", "testns1")); } +TEST(GetDeducedType, KwAutoExpansion) { + struct Test { + StringRef AnnotatedCode; + const char *DeducedType; + } Tests[] = { + {"^auto i = 0;", "int"}, + {"^auto f(){ return 1;};", "int"}, + }; + for (Test T : Tests) { + Annotations File(T.AnnotatedCode); + auto AST = TestTU::withCode(File.code()).build(); + ASSERT_TRUE(AST.getDiagnostics().empty()) + << AST.getDiagnostics().begin()->Message; + SourceManagerForFile SM("foo.cpp", File.code()); + + for (Position Pos : File.points()) { + auto Location = sourceLocationInMainFile(SM.get(), Pos); + ASSERT_TRUE(!!Location) << llvm::toString(Location.takeError()); + auto DeducedType = getDeducedType(AST.getASTContext(), *Location); + EXPECT_EQ(DeducedType->getAsString(), T.DeducedType); + } + } +} + } // namespace } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/unittests/CMakeLists.txt b/clang-tools-extra/clangd/unittests/CMakeLists.txt --- a/clang-tools-extra/clangd/unittests/CMakeLists.txt +++ b/clang-tools-extra/clangd/unittests/CMakeLists.txt @@ -47,6 +47,7 @@ GlobalCompilationDatabaseTests.cpp HeadersTests.cpp HeaderSourceSwitchTests.cpp + HoverTests.cpp IndexActionTests.cpp IndexTests.cpp JSONTransportTests.cpp diff --git a/clang-tools-extra/clangd/unittests/XRefsTests.cpp b/clang-tools-extra/clangd/unittests/HoverTests.cpp copy from clang-tools-extra/clangd/unittests/XRefsTests.cpp copy to clang-tools-extra/clangd/unittests/HoverTests.cpp --- a/clang-tools-extra/clangd/unittests/XRefsTests.cpp +++ b/clang-tools-extra/clangd/unittests/HoverTests.cpp @@ -1,608 +1,24 @@ -//===-- XRefsTests.cpp ---------------------------*- C++ -*--------------===// +//===-- HoverTests.cpp ----------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// + #include "Annotations.h" -#include "Compiler.h" -#include "Matchers.h" -#include "ParsedAST.h" -#include "Protocol.h" -#include "SourceCode.h" -#include "SyncAPI.h" -#include "TestFS.h" +#include "Hover.h" #include "TestIndex.h" #include "TestTU.h" -#include "XRefs.h" -#include "index/FileIndex.h" #include "index/MemIndex.h" -#include "index/SymbolCollector.h" -#include "clang/AST/Decl.h" -#include "clang/Basic/SourceLocation.h" -#include "clang/Index/IndexingAction.h" -#include "llvm/ADT/None.h" -#include "llvm/ADT/StringRef.h" -#include "llvm/Support/Casting.h" -#include "llvm/Support/Error.h" -#include "llvm/Support/Path.h" -#include "llvm/Support/ScopedPrinter.h" + #include "gmock/gmock.h" #include "gtest/gtest.h" -#include -#include namespace clang { namespace clangd { namespace { -using ::testing::ElementsAre; -using ::testing::IsEmpty; -using ::testing::Matcher; -using ::testing::UnorderedElementsAreArray; - -class IgnoreDiagnostics : public DiagnosticsConsumer { - void onDiagnosticsReady(PathRef File, - std::vector Diagnostics) override {} -}; - -MATCHER_P2(FileRange, File, Range, "") { - return Location{URIForFile::canonicalize(File, testRoot()), Range} == arg; -} - -// Extracts ranges from an annotated example, and constructs a matcher for a -// highlight set. Ranges should be named $read/$write as appropriate. -Matcher &> -HighlightsFrom(const Annotations &Test) { - std::vector Expected; - auto Add = [&](const Range &R, DocumentHighlightKind K) { - Expected.emplace_back(); - Expected.back().range = R; - Expected.back().kind = K; - }; - for (const auto &Range : Test.ranges()) - Add(Range, DocumentHighlightKind::Text); - for (const auto &Range : Test.ranges("read")) - Add(Range, DocumentHighlightKind::Read); - for (const auto &Range : Test.ranges("write")) - Add(Range, DocumentHighlightKind::Write); - return UnorderedElementsAreArray(Expected); -} - -TEST(HighlightsTest, All) { - const char *Tests[] = { - R"cpp(// Local variable - int main() { - int [[bonjour]]; - $write[[^bonjour]] = 2; - int test1 = $read[[bonjour]]; - } - )cpp", - - R"cpp(// Struct - namespace ns1 { - struct [[MyClass]] { - static void foo([[MyClass]]*) {} - }; - } // namespace ns1 - int main() { - ns1::[[My^Class]]* Params; - } - )cpp", - - R"cpp(// Function - int [[^foo]](int) {} - int main() { - [[foo]]([[foo]](42)); - auto *X = &[[foo]]; - } - )cpp", - - R"cpp(// Function parameter in decl - void foo(int [[^bar]]); - )cpp", - }; - for (const char *Test : Tests) { - Annotations T(Test); - auto AST = TestTU::withCode(T.code()).build(); - EXPECT_THAT(findDocumentHighlights(AST, T.point()), HighlightsFrom(T)) - << Test; - } -} - -MATCHER_P3(Sym, Name, Decl, DefOrNone, "") { - llvm::Optional Def = DefOrNone; - if (Name != arg.Name) { - *result_listener << "Name is " << arg.Name; - return false; - } - if (Decl != arg.PreferredDeclaration.range) { - *result_listener << "Declaration is " - << llvm::to_string(arg.PreferredDeclaration); - return false; - } - if (Def && !arg.Definition) { - *result_listener << "Has no definition"; - return false; - } - if (Def && arg.Definition->range != *Def) { - *result_listener << "Definition is " << llvm::to_string(arg.Definition); - return false; - } - return true; -} -::testing::Matcher Sym(std::string Name, Range Decl) { - return Sym(Name, Decl, llvm::None); -} -MATCHER_P(Sym, Name, "") { return arg.Name == Name; } - -MATCHER_P(RangeIs, R, "") { return arg.range == R; } - -TEST(LocateSymbol, WithIndex) { - Annotations SymbolHeader(R"cpp( - class $forward[[Forward]]; - class $foo[[Foo]] {}; - - void $f1[[f1]](); - - inline void $f2[[f2]]() {} - )cpp"); - Annotations SymbolCpp(R"cpp( - class $forward[[forward]] {}; - void $f1[[f1]]() {} - )cpp"); - - TestTU TU; - TU.Code = SymbolCpp.code(); - TU.HeaderCode = SymbolHeader.code(); - auto Index = TU.index(); - auto LocateWithIndex = [&Index](const Annotations &Main) { - auto AST = TestTU::withCode(Main.code()).build(); - return clangd::locateSymbolAt(AST, Main.point(), Index.get()); - }; - - Annotations Test(R"cpp(// only declaration in AST. - void [[f1]](); - int main() { - ^f1(); - } - )cpp"); - EXPECT_THAT(LocateWithIndex(Test), - ElementsAre(Sym("f1", Test.range(), SymbolCpp.range("f1")))); - - Test = Annotations(R"cpp(// definition in AST. - void [[f1]]() {} - int main() { - ^f1(); - } - )cpp"); - EXPECT_THAT(LocateWithIndex(Test), - ElementsAre(Sym("f1", SymbolHeader.range("f1"), Test.range()))); - - Test = Annotations(R"cpp(// forward declaration in AST. - class [[Foo]]; - F^oo* create(); - )cpp"); - EXPECT_THAT(LocateWithIndex(Test), - ElementsAre(Sym("Foo", Test.range(), SymbolHeader.range("foo")))); - - Test = Annotations(R"cpp(// defintion in AST. - class [[Forward]] {}; - F^orward create(); - )cpp"); - EXPECT_THAT( - LocateWithIndex(Test), - ElementsAre(Sym("Forward", SymbolHeader.range("forward"), Test.range()))); -} - -TEST(LocateSymbol, WithIndexPreferredLocation) { - Annotations SymbolHeader(R"cpp( - class $p[[Proto]] {}; - void $f[[func]]() {}; - )cpp"); - TestTU TU; - TU.HeaderCode = SymbolHeader.code(); - TU.HeaderFilename = "x.proto"; // Prefer locations in codegen files. - auto Index = TU.index(); - - Annotations Test(R"cpp(// only declaration in AST. - // Shift to make range different. - class Proto; - void func() {} - P$p^roto* create() { - fu$f^nc(); - return nullptr; - } - )cpp"); - - auto AST = TestTU::withCode(Test.code()).build(); - { - auto Locs = clangd::locateSymbolAt(AST, Test.point("p"), Index.get()); - auto CodeGenLoc = SymbolHeader.range("p"); - EXPECT_THAT(Locs, ElementsAre(Sym("Proto", CodeGenLoc, CodeGenLoc))); - } - { - auto Locs = clangd::locateSymbolAt(AST, Test.point("f"), Index.get()); - auto CodeGenLoc = SymbolHeader.range("f"); - EXPECT_THAT(Locs, ElementsAre(Sym("func", CodeGenLoc, CodeGenLoc))); - } -} - -TEST(LocateSymbol, All) { - // Ranges in tests: - // $decl is the declaration location (if absent, no symbol is located) - // $def is the definition location (if absent, symbol has no definition) - // unnamed range becomes both $decl and $def. - const char *Tests[] = { - R"cpp(// Local variable - int main() { - int [[bonjour]]; - ^bonjour = 2; - int test1 = bonjour; - } - )cpp", - - R"cpp(// Struct - namespace ns1 { - struct [[MyClass]] {}; - } // namespace ns1 - int main() { - ns1::My^Class* Params; - } - )cpp", - - R"cpp(// Function definition via pointer - int [[foo]](int) {} - int main() { - auto *X = &^foo; - } - )cpp", - - R"cpp(// Function declaration via call - int $decl[[foo]](int); - int main() { - return ^foo(42); - } - )cpp", - - R"cpp(// Field - struct Foo { int [[x]]; }; - int main() { - Foo bar; - bar.^x; - } - )cpp", - - R"cpp(// Field, member initializer - struct Foo { - int [[x]]; - Foo() : ^x(0) {} - }; - )cpp", - - R"cpp(// Field, GNU old-style field designator - struct Foo { int [[x]]; }; - int main() { - Foo bar = { ^x : 1 }; - } - )cpp", - - R"cpp(// Field, field designator - struct Foo { int [[x]]; }; - int main() { - Foo bar = { .^x = 2 }; - } - )cpp", - - R"cpp(// Method call - struct Foo { int $decl[[x]](); }; - int main() { - Foo bar; - bar.^x(); - } - )cpp", - - R"cpp(// Typedef - typedef int $decl[[Foo]]; - int main() { - ^Foo bar; - } - )cpp", - - R"cpp(// Template type parameter - template - void foo() { ^T t; } - )cpp", - - R"cpp(// Template template type parameter - template class [[T]]> - void foo() { ^T t; } - )cpp", - - R"cpp(// Namespace - namespace $decl[[ns]] { - struct Foo { static void bar(); } - } // namespace ns - int main() { ^ns::Foo::bar(); } - )cpp", - - R"cpp(// Macro - #define MACRO 0 - #define [[MACRO]] 1 - int main() { return ^MACRO; } - #define MACRO 2 - #undef macro - )cpp", - - R"cpp(// Macro - class TTT { public: int a; }; - #define [[FF]](S) if (int b = S.a) {} - void f() { - TTT t; - F^F(t); - } - )cpp", - - R"cpp(// Macro argument - int [[i]]; - #define ADDRESSOF(X) &X; - int *j = ADDRESSOF(^i); - )cpp", - - R"cpp(// Symbol concatenated inside macro (not supported) - int *pi; - #define POINTER(X) p # X; - int i = *POINTER(^i); - )cpp", - - R"cpp(// Forward class declaration - class Foo; - class [[Foo]] {}; - F^oo* foo(); - )cpp", - - R"cpp(// Function declaration - void foo(); - void g() { f^oo(); } - void [[foo]]() {} - )cpp", - - R"cpp( - #define FF(name) class name##_Test {}; - [[FF]](my); - void f() { my^_Test a; } - )cpp", - - R"cpp( - #define FF() class [[Test]] {}; - FF(); - void f() { T^est a; } - )cpp", - - R"cpp(// explicit template specialization - template - struct Foo { void bar() {} }; - - template <> - struct [[Foo]] { void bar() {} }; - - void foo() { - Foo abc; - Fo^o b; - } - )cpp", - - R"cpp(// implicit template specialization - template - struct [[Foo]] { void bar() {} }; - template <> - struct Foo { void bar() {} }; - void foo() { - Fo^o abc; - Foo b; - } - )cpp", - - R"cpp(// partial template specialization - template - struct Foo { void bar() {} }; - template - struct [[Foo]] { void bar() {} }; - ^Foo x; - )cpp", - - R"cpp(// function template specializations - template - void foo(T) {} - template <> - void [[foo]](int) {} - void bar() { - fo^o(10); - } - )cpp", - - R"cpp(// variable template decls - template - T var = T(); - - template <> - double [[var]] = 10; - - double y = va^r; - )cpp", - - R"cpp(// No implicit constructors - class X { - X(X&& x) = default; - }; - X [[makeX]]() {} - void foo() { - auto x = m^akeX(); - } - )cpp", - - R"cpp( - struct X { - X& [[operator]]++() {} - }; - void foo(X& x) { - +^+x; - } - )cpp", - }; - for (const char *Test : Tests) { - Annotations T(Test); - llvm::Optional WantDecl; - llvm::Optional WantDef; - if (!T.ranges().empty()) - WantDecl = WantDef = T.range(); - if (!T.ranges("decl").empty()) - WantDecl = T.range("decl"); - if (!T.ranges("def").empty()) - WantDef = T.range("def"); - - TestTU TU; - TU.Code = T.code(); - - // FIXME: Auto-completion in a template requires disabling delayed template - // parsing. - TU.ExtraArgs.push_back("-fno-delayed-template-parsing"); - - auto AST = TU.build(); - auto Results = locateSymbolAt(AST, T.point()); - - if (!WantDecl) { - EXPECT_THAT(Results, IsEmpty()) << Test; - } else { - ASSERT_THAT(Results, ::testing::SizeIs(1)) << Test; - EXPECT_EQ(Results[0].PreferredDeclaration.range, *WantDecl) << Test; - llvm::Optional GotDef; - if (Results[0].Definition) - GotDef = Results[0].Definition->range; - EXPECT_EQ(WantDef, GotDef) << Test; - } - } -} - -TEST(LocateSymbol, Ambiguous) { - auto T = Annotations(R"cpp( - struct Foo { - Foo(); - Foo(Foo&&); - $ConstructorLoc[[Foo]](const char*); - }; - - Foo f(); - - void g(Foo foo); - - void call() { - const char* str = "123"; - Foo a = $1^str; - Foo b = Foo($2^str); - Foo c = $3^f(); - $4^g($5^f()); - g($6^str); - Foo ab$7^c; - Foo ab$8^cd("asdf"); - Foo foox = Fo$9^o("asdf"); - Foo abcde$10^("asdf"); - Foo foox2 = Foo$11^("asdf"); - } - )cpp"); - auto AST = TestTU::withCode(T.code()).build(); - // Ordered assertions are deliberate: we expect a predictable order. - EXPECT_THAT(locateSymbolAt(AST, T.point("1")), ElementsAre(Sym("str"))); - EXPECT_THAT(locateSymbolAt(AST, T.point("2")), ElementsAre(Sym("str"))); - EXPECT_THAT(locateSymbolAt(AST, T.point("3")), ElementsAre(Sym("f"))); - EXPECT_THAT(locateSymbolAt(AST, T.point("4")), ElementsAre(Sym("g"))); - EXPECT_THAT(locateSymbolAt(AST, T.point("5")), ElementsAre(Sym("f"))); - EXPECT_THAT(locateSymbolAt(AST, T.point("6")), ElementsAre(Sym("str"))); - // FIXME: Target the constructor as well. - EXPECT_THAT(locateSymbolAt(AST, T.point("7")), ElementsAre(Sym("abc"))); - // FIXME: Target the constructor as well. - EXPECT_THAT(locateSymbolAt(AST, T.point("8")), ElementsAre(Sym("abcd"))); - // FIXME: Target the constructor as well. - EXPECT_THAT(locateSymbolAt(AST, T.point("9")), ElementsAre(Sym("Foo"))); - EXPECT_THAT(locateSymbolAt(AST, T.point("10")), - ElementsAre(Sym("Foo", T.range("ConstructorLoc")))); - EXPECT_THAT(locateSymbolAt(AST, T.point("11")), - ElementsAre(Sym("Foo", T.range("ConstructorLoc")))); -} - -TEST(LocateSymbol, TemplateTypedefs) { - auto T = Annotations(R"cpp( - template struct function {}; - template using callback = function; - - c^allback foo; - )cpp"); - auto AST = TestTU::withCode(T.code()).build(); - EXPECT_THAT(locateSymbolAt(AST, T.point()), ElementsAre(Sym("callback"))); -} - -TEST(LocateSymbol, RelPathsInCompileCommand) { - // The source is in "/clangd-test/src". - // We build in "/clangd-test/build". - - Annotations SourceAnnotations(R"cpp( -#include "header_in_preamble.h" -int [[foo]]; -#include "header_not_in_preamble.h" -int baz = f$p1^oo + bar_pre$p2^amble + bar_not_pre$p3^amble; -)cpp"); - - Annotations HeaderInPreambleAnnotations(R"cpp( -int [[bar_preamble]]; -)cpp"); - - Annotations HeaderNotInPreambleAnnotations(R"cpp( -int [[bar_not_preamble]]; -)cpp"); - - // Make the compilation paths appear as ../src/foo.cpp in the compile - // commands. - SmallString<32> RelPathPrefix(".."); - llvm::sys::path::append(RelPathPrefix, "src"); - std::string BuildDir = testPath("build"); - MockCompilationDatabase CDB(BuildDir, RelPathPrefix); - - IgnoreDiagnostics DiagConsumer; - MockFSProvider FS; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - // Fill the filesystem. - auto FooCpp = testPath("src/foo.cpp"); - FS.Files[FooCpp] = ""; - auto HeaderInPreambleH = testPath("src/header_in_preamble.h"); - FS.Files[HeaderInPreambleH] = HeaderInPreambleAnnotations.code(); - auto HeaderNotInPreambleH = testPath("src/header_not_in_preamble.h"); - FS.Files[HeaderNotInPreambleH] = HeaderNotInPreambleAnnotations.code(); - - runAddDocument(Server, FooCpp, SourceAnnotations.code()); - - // Go to a definition in main source file. - auto Locations = - runLocateSymbolAt(Server, FooCpp, SourceAnnotations.point("p1")); - EXPECT_TRUE(bool(Locations)) << "findDefinitions returned an error"; - EXPECT_THAT(*Locations, ElementsAre(Sym("foo", SourceAnnotations.range()))); - - // Go to a definition in header_in_preamble.h. - Locations = runLocateSymbolAt(Server, FooCpp, SourceAnnotations.point("p2")); - EXPECT_TRUE(bool(Locations)) << "findDefinitions returned an error"; - EXPECT_THAT( - *Locations, - ElementsAre(Sym("bar_preamble", HeaderInPreambleAnnotations.range()))); - - // Go to a definition in header_not_in_preamble.h. - Locations = runLocateSymbolAt(Server, FooCpp, SourceAnnotations.point("p3")); - EXPECT_TRUE(bool(Locations)) << "findDefinitions returned an error"; - EXPECT_THAT(*Locations, - ElementsAre(Sym("bar_not_preamble", - HeaderNotInPreambleAnnotations.range()))); -} - TEST(Hover, Structured) { struct { const char *const Code; @@ -931,7 +347,7 @@ )cpp", [](HoverInfo &HI) { HI.Name = "class (lambda)"; - HI.Kind = SymbolKind::Variable; + HI.Kind = SymbolKind::Class; }}, // auto on template instantiation {R"cpp( @@ -942,7 +358,7 @@ )cpp", [](HoverInfo &HI) { HI.Name = "class Foo"; - HI.Kind = SymbolKind::Variable; + HI.Kind = SymbolKind::Class; }}, // auto on specialized template {R"cpp( @@ -954,7 +370,7 @@ )cpp", [](HoverInfo &HI) { HI.Name = "class Foo"; - HI.Kind = SymbolKind::Variable; + HI.Kind = SymbolKind::Class; }}, // macro @@ -1895,445 +1311,6 @@ } } -TEST(GoToInclude, All) { - MockFSProvider FS; - IgnoreDiagnostics DiagConsumer; - MockCompilationDatabase CDB; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - auto FooCpp = testPath("foo.cpp"); - const char *SourceContents = R"cpp( - #include ^"$2^foo.h$3^" - #include "$4^invalid.h" - int b = a; - // test - int foo; - #in$5^clude "$6^foo.h"$7^ - )cpp"; - Annotations SourceAnnotations(SourceContents); - FS.Files[FooCpp] = SourceAnnotations.code(); - auto FooH = testPath("foo.h"); - - const char *HeaderContents = R"cpp([[]]#pragma once - int a; - )cpp"; - Annotations HeaderAnnotations(HeaderContents); - FS.Files[FooH] = HeaderAnnotations.code(); - - Server.addDocument(FooH, HeaderAnnotations.code()); - Server.addDocument(FooCpp, SourceAnnotations.code()); - - // Test include in preamble. - auto Locations = runLocateSymbolAt(Server, FooCpp, SourceAnnotations.point()); - ASSERT_TRUE(bool(Locations)) << "locateSymbolAt returned an error"; - EXPECT_THAT(*Locations, ElementsAre(Sym("foo.h", HeaderAnnotations.range()))); - - // Test include in preamble, last char. - Locations = runLocateSymbolAt(Server, FooCpp, SourceAnnotations.point("2")); - ASSERT_TRUE(bool(Locations)) << "locateSymbolAt returned an error"; - EXPECT_THAT(*Locations, ElementsAre(Sym("foo.h", HeaderAnnotations.range()))); - - Locations = runLocateSymbolAt(Server, FooCpp, SourceAnnotations.point("3")); - ASSERT_TRUE(bool(Locations)) << "locateSymbolAt returned an error"; - EXPECT_THAT(*Locations, ElementsAre(Sym("foo.h", HeaderAnnotations.range()))); - - // Test include outside of preamble. - Locations = runLocateSymbolAt(Server, FooCpp, SourceAnnotations.point("6")); - ASSERT_TRUE(bool(Locations)) << "locateSymbolAt returned an error"; - EXPECT_THAT(*Locations, ElementsAre(Sym("foo.h", HeaderAnnotations.range()))); - - // Test a few positions that do not result in Locations. - Locations = runLocateSymbolAt(Server, FooCpp, SourceAnnotations.point("4")); - ASSERT_TRUE(bool(Locations)) << "locateSymbolAt returned an error"; - EXPECT_THAT(*Locations, IsEmpty()); - - Locations = runLocateSymbolAt(Server, FooCpp, SourceAnnotations.point("5")); - ASSERT_TRUE(bool(Locations)) << "locateSymbolAt returned an error"; - EXPECT_THAT(*Locations, ElementsAre(Sym("foo.h", HeaderAnnotations.range()))); - - Locations = runLocateSymbolAt(Server, FooCpp, SourceAnnotations.point("7")); - ASSERT_TRUE(bool(Locations)) << "locateSymbolAt returned an error"; - EXPECT_THAT(*Locations, ElementsAre(Sym("foo.h", HeaderAnnotations.range()))); - - // Objective C #import directive. - Annotations ObjC(R"objc( - #import "^foo.h" - )objc"); - auto FooM = testPath("foo.m"); - FS.Files[FooM] = ObjC.code(); - - Server.addDocument(FooM, ObjC.code()); - Locations = runLocateSymbolAt(Server, FooM, ObjC.point()); - ASSERT_TRUE(bool(Locations)) << "locateSymbolAt returned an error"; - EXPECT_THAT(*Locations, ElementsAre(Sym("foo.h", HeaderAnnotations.range()))); -} - -TEST(LocateSymbol, WithPreamble) { - // Test stragety: AST should always use the latest preamble instead of last - // good preamble. - MockFSProvider FS; - IgnoreDiagnostics DiagConsumer; - MockCompilationDatabase CDB; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - auto FooCpp = testPath("foo.cpp"); - // The trigger locations must be the same. - Annotations FooWithHeader(R"cpp(#include "fo^o.h")cpp"); - Annotations FooWithoutHeader(R"cpp(double [[fo^o]]();)cpp"); - - FS.Files[FooCpp] = FooWithHeader.code(); - - auto FooH = testPath("foo.h"); - Annotations FooHeader(R"cpp([[]])cpp"); - FS.Files[FooH] = FooHeader.code(); - - runAddDocument(Server, FooCpp, FooWithHeader.code()); - // LocateSymbol goes to a #include file: the result comes from the preamble. - EXPECT_THAT( - cantFail(runLocateSymbolAt(Server, FooCpp, FooWithHeader.point())), - ElementsAre(Sym("foo.h", FooHeader.range()))); - - // Only preamble is built, and no AST is built in this request. - Server.addDocument(FooCpp, FooWithoutHeader.code(), WantDiagnostics::No); - // We build AST here, and it should use the latest preamble rather than the - // stale one. - EXPECT_THAT( - cantFail(runLocateSymbolAt(Server, FooCpp, FooWithoutHeader.point())), - ElementsAre(Sym("foo", FooWithoutHeader.range()))); - - // Reset test environment. - runAddDocument(Server, FooCpp, FooWithHeader.code()); - // Both preamble and AST are built in this request. - Server.addDocument(FooCpp, FooWithoutHeader.code(), WantDiagnostics::Yes); - // Use the AST being built in above request. - EXPECT_THAT( - cantFail(runLocateSymbolAt(Server, FooCpp, FooWithoutHeader.point())), - ElementsAre(Sym("foo", FooWithoutHeader.range()))); -} - -TEST(FindReferences, WithinAST) { - const char *Tests[] = { - R"cpp(// Local variable - int main() { - int [[foo]]; - [[^foo]] = 2; - int test1 = [[foo]]; - } - )cpp", - - R"cpp(// Struct - namespace ns1 { - struct [[Foo]] {}; - } // namespace ns1 - int main() { - ns1::[[Fo^o]]* Params; - } - )cpp", - - R"cpp(// Forward declaration - class [[Foo]]; - class [[Foo]] {} - int main() { - [[Fo^o]] foo; - } - )cpp", - - R"cpp(// Function - int [[foo]](int) {} - int main() { - auto *X = &[[^foo]]; - [[foo]](42) - } - )cpp", - - R"cpp(// Field - struct Foo { - int [[foo]]; - Foo() : [[foo]](0) {} - }; - int main() { - Foo f; - f.[[f^oo]] = 1; - } - )cpp", - - R"cpp(// Method call - struct Foo { int [[foo]](); }; - int Foo::[[foo]]() {} - int main() { - Foo f; - f.[[^foo]](); - } - )cpp", - - R"cpp(// Constructor - struct Foo { - [[F^oo]](int); - }; - void foo() { - Foo f = [[Foo]](42); - } - )cpp", - - R"cpp(// Typedef - typedef int [[Foo]]; - int main() { - [[^Foo]] bar; - } - )cpp", - - R"cpp(// Namespace - namespace [[ns]] { - struct Foo {}; - } // namespace ns - int main() { [[^ns]]::Foo foo; } - )cpp", - }; - for (const char *Test : Tests) { - Annotations T(Test); - auto AST = TestTU::withCode(T.code()).build(); - std::vector> ExpectedLocations; - for (const auto &R : T.ranges()) - ExpectedLocations.push_back(RangeIs(R)); - EXPECT_THAT(findReferences(AST, T.point(), 0), - ElementsAreArray(ExpectedLocations)) - << Test; - } -} - -TEST(FindReferences, ExplicitSymbols) { - const char *Tests[] = { - R"cpp( - struct Foo { Foo* [[self]]() const; }; - void f() { - Foo foo; - if (Foo* T = foo.[[^self]]()) {} // Foo member call expr. - } - )cpp", - - R"cpp( - struct Foo { Foo(int); }; - Foo f() { - int [[b]]; - return [[^b]]; // Foo constructor expr. - } - )cpp", - - R"cpp( - struct Foo {}; - void g(Foo); - Foo [[f]](); - void call() { - g([[^f]]()); // Foo constructor expr. - } - )cpp", - - R"cpp( - void [[foo]](int); - void [[foo]](double); - - namespace ns { - using ::[[fo^o]]; - } - )cpp", - - R"cpp( - struct X { - operator bool(); - }; - - int test() { - X [[a]]; - [[a]].operator bool(); - if ([[a^]]) {} // ignore implicit conversion-operator AST node - } - )cpp", - }; - for (const char *Test : Tests) { - Annotations T(Test); - auto AST = TestTU::withCode(T.code()).build(); - std::vector> ExpectedLocations; - for (const auto &R : T.ranges()) - ExpectedLocations.push_back(RangeIs(R)); - ASSERT_THAT(ExpectedLocations, Not(IsEmpty())); - EXPECT_THAT(findReferences(AST, T.point(), 0), - ElementsAreArray(ExpectedLocations)) - << Test; - } -} - -TEST(FindReferences, NeedsIndex) { - const char *Header = "int foo();"; - Annotations Main("int main() { [[f^oo]](); }"); - TestTU TU; - TU.Code = Main.code(); - TU.HeaderCode = Header; - auto AST = TU.build(); - - // References in main file are returned without index. - EXPECT_THAT(findReferences(AST, Main.point(), 0, /*Index=*/nullptr), - ElementsAre(RangeIs(Main.range()))); - Annotations IndexedMain(R"cpp( - int main() { [[f^oo]](); } - )cpp"); - - // References from indexed files are included. - TestTU IndexedTU; - IndexedTU.Code = IndexedMain.code(); - IndexedTU.Filename = "Indexed.cpp"; - IndexedTU.HeaderCode = Header; - EXPECT_THAT(findReferences(AST, Main.point(), 0, IndexedTU.index().get()), - ElementsAre(RangeIs(Main.range()), RangeIs(IndexedMain.range()))); - - EXPECT_EQ(1u, findReferences(AST, Main.point(), /*Limit*/ 1, - IndexedTU.index().get()) - .size()); - - // If the main file is in the index, we don't return duplicates. - // (even if the references are in a different location) - TU.Code = ("\n\n" + Main.code()).str(); - EXPECT_THAT(findReferences(AST, Main.point(), 0, TU.index().get()), - ElementsAre(RangeIs(Main.range()))); -} - -TEST(FindReferences, NoQueryForLocalSymbols) { - struct RecordingIndex : public MemIndex { - mutable Optional> RefIDs; - bool refs(const RefsRequest &Req, - llvm::function_ref) const override { - RefIDs = Req.IDs; - return false; - } - }; - - struct Test { - StringRef AnnotatedCode; - bool WantQuery; - } Tests[] = { - {"int ^x;", true}, - // For now we don't assume header structure which would allow skipping. - {"namespace { int ^x; }", true}, - {"static int ^x;", true}, - // Anything in a function certainly can't be referenced though. - {"void foo() { int ^x; }", false}, - {"void foo() { struct ^x{}; }", false}, - {"auto lambda = []{ int ^x; };", false}, - }; - for (Test T : Tests) { - Annotations File(T.AnnotatedCode); - RecordingIndex Rec; - auto AST = TestTU::withCode(File.code()).build(); - findReferences(AST, File.point(), 0, &Rec); - if (T.WantQuery) - EXPECT_NE(Rec.RefIDs, None) << T.AnnotatedCode; - else - EXPECT_EQ(Rec.RefIDs, None) << T.AnnotatedCode; - } -} - -TEST(GetDeducedType, KwAutoExpansion) { - struct Test { - StringRef AnnotatedCode; - const char *DeducedType; - } Tests[] = { - {"^auto i = 0;", "int"}, - {"^auto f(){ return 1;};", "int"}, - }; - for (Test T : Tests) { - Annotations File(T.AnnotatedCode); - auto AST = TestTU::withCode(File.code()).build(); - ASSERT_TRUE(AST.getDiagnostics().empty()) - << AST.getDiagnostics().begin()->Message; - SourceManagerForFile SM("foo.cpp", File.code()); - - for (Position Pos : File.points()) { - auto Location = sourceLocationInMainFile(SM.get(), Pos); - ASSERT_TRUE(!!Location) << llvm::toString(Location.takeError()); - auto DeducedType = getDeducedType(AST, *Location); - EXPECT_EQ(DeducedType->getAsString(), T.DeducedType); - } - } -} - -TEST(GetNonLocalDeclRefs, All) { - struct Case { - llvm::StringRef AnnotatedCode; - std::vector ExpectedDecls; - } Cases[] = { - { - // VarDecl and ParamVarDecl - R"cpp( - void bar(); - void ^foo(int baz) { - int x = 10; - bar(); - })cpp", - {"bar"}, - }, - { - // Method from class - R"cpp( - class Foo { public: void foo(); }; - class Bar { - void foo(); - void bar(); - }; - void Bar::^foo() { - Foo f; - bar(); - f.foo(); - })cpp", - {"Bar", "Bar::bar", "Foo", "Foo::foo"}, - }, - { - // Local types - R"cpp( - void ^foo() { - class Foo { public: void foo() {} }; - class Bar { public: void bar() {} }; - Foo f; - Bar b; - b.bar(); - f.foo(); - })cpp", - {}, - }, - { - // Template params - R"cpp( - template class Q> - void ^foo() { - T x; - Q y; - })cpp", - {}, - }, - }; - for (const Case &C : Cases) { - Annotations File(C.AnnotatedCode); - auto AST = TestTU::withCode(File.code()).build(); - ASSERT_TRUE(AST.getDiagnostics().empty()) - << AST.getDiagnostics().begin()->Message; - SourceLocation SL = llvm::cantFail( - sourceLocationInMainFile(AST.getSourceManager(), File.point())); - - const FunctionDecl *FD = - llvm::dyn_cast(&findDecl(AST, [SL](const NamedDecl &ND) { - return ND.getLocation() == SL && llvm::isa(ND); - })); - ASSERT_NE(FD, nullptr); - - auto NonLocalDeclRefs = getNonLocalDeclRefs(AST, FD); - std::vector Names; - for (const Decl *D : NonLocalDeclRefs) { - if (const auto *ND = llvm::dyn_cast(D)) - Names.push_back(ND->getQualifiedNameAsString()); - } - EXPECT_THAT(Names, UnorderedElementsAreArray(C.ExpectedDecls)) - << File.code(); - } -} - } // namespace } // 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 @@ -603,1298 +603,6 @@ 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 { public: Foo(int); }; - Foo [[fo^o]] = Foo(5); - )cpp", - [](HoverInfo &HI) { - HI.NamespaceScope = ""; - HI.Name = "foo"; - HI.Kind = SymbolKind::Variable; - HI.Definition = "Foo foo = Foo(5)"; - 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