diff options
Diffstat (limited to 'clang-tools-extra/clangd')
47 files changed, 2572 insertions, 435 deletions
diff --git a/clang-tools-extra/clangd/AST.cpp b/clang-tools-extra/clangd/AST.cpp index f2631e5..2f46ecc 100644 --- a/clang-tools-extra/clangd/AST.cpp +++ b/clang-tools-extra/clangd/AST.cpp @@ -102,54 +102,78 @@ getUsingNamespaceDirectives(const DeclContext *DestContext, // ancestor is redundant, therefore we stop at lowest common ancestor. // In addition to that stops early whenever IsVisible returns true. This can be // used to implement support for "using namespace" decls. -std::string -getQualification(ASTContext &Context, const DeclContext *DestContext, - const DeclContext *SourceContext, - llvm::function_ref<bool(NestedNameSpecifier *)> IsVisible) { - std::vector<const NestedNameSpecifier *> Parents; - bool ReachedNS = false; +std::string getQualification(ASTContext &Context, + const DeclContext *DestContext, + const DeclContext *SourceContext, + llvm::function_ref<bool(const Decl *)> IsVisible) { + std::vector<const Decl *> Parents; + [[maybe_unused]] bool ReachedNS = false; for (const DeclContext *CurContext = SourceContext; CurContext; CurContext = CurContext->getLookupParent()) { // Stop once we reach a common ancestor. if (CurContext->Encloses(DestContext)) break; - NestedNameSpecifier *NNS = nullptr; + const Decl *CurD; if (auto *TD = llvm::dyn_cast<TagDecl>(CurContext)) { // There can't be any more tag parents after hitting a namespace. assert(!ReachedNS); - (void)ReachedNS; - NNS = NestedNameSpecifier::Create(Context, nullptr, TD->getTypeForDecl()); + CurD = TD; } else if (auto *NSD = llvm::dyn_cast<NamespaceDecl>(CurContext)) { ReachedNS = true; - NNS = NestedNameSpecifier::Create(Context, nullptr, NSD); // Anonymous and inline namespace names are not spelled while qualifying // a name, so skip those. if (NSD->isAnonymousNamespace() || NSD->isInlineNamespace()) continue; + CurD = NSD; } else { // Other types of contexts cannot be spelled in code, just skip over // them. continue; } // Stop if this namespace is already visible at DestContext. - if (IsVisible(NNS)) + if (IsVisible(CurD)) break; - Parents.push_back(NNS); + Parents.push_back(CurD); + } + + // Go over the declarations in reverse order, since we stored inner-most + // parent first. + NestedNameSpecifier Qualifier = std::nullopt; + bool IsFirst = true; + for (const auto *CurD : llvm::reverse(Parents)) { + if (auto *TD = llvm::dyn_cast<TagDecl>(CurD)) { + QualType T; + if (const auto *RD = dyn_cast<CXXRecordDecl>(TD); + ClassTemplateDecl *CTD = RD->getDescribedClassTemplate()) { + ArrayRef<TemplateArgument> Args; + if (const auto *SD = dyn_cast<ClassTemplateSpecializationDecl>(RD)) + Args = SD->getTemplateArgs().asArray(); + else + Args = CTD->getTemplateParameters()->getInjectedTemplateArgs(Context); + T = Context.getTemplateSpecializationType( + ElaboratedTypeKeyword::None, + Context.getQualifiedTemplateName( + Qualifier, /*TemplateKeyword=*/!IsFirst, TemplateName(CTD)), + Args, /*CanonicalArgs=*/{}, Context.getCanonicalTagType(RD)); + } else { + T = Context.getTagType(ElaboratedTypeKeyword::None, Qualifier, TD, + /*OwnsTag=*/false); + } + Qualifier = NestedNameSpecifier(T.getTypePtr()); + } else { + Qualifier = + NestedNameSpecifier(Context, cast<NamespaceDecl>(CurD), Qualifier); + } + IsFirst = false; } + if (!Qualifier) + return ""; - // Go over name-specifiers in reverse order to create necessary qualification, - // since we stored inner-most parent first. std::string Result; llvm::raw_string_ostream OS(Result); - for (const auto *Parent : llvm::reverse(Parents)) { - if (Parent != *Parents.rbegin() && Parent->isDependent() && - Parent->getAsRecordDecl() && - Parent->getAsRecordDecl()->getDescribedClassTemplate()) - OS << "template "; - Parent->print(OS, Context.getPrintingPolicy()); - } + Qualifier.print(OS, Context.getPrintingPolicy()); return OS.str(); } @@ -187,6 +211,7 @@ std::string printQualifiedName(const NamedDecl &ND) { // include them, but at query time it's hard to find all the inline // namespaces to query: the preamble doesn't have a dedicated list. Policy.SuppressUnwrittenScope = true; + Policy.SuppressScope = true; // (unnamed struct), not (unnamed struct at /path/to/foo.cc:42:1). // In clangd, context is usually available and paths are mostly noise. Policy.AnonymousTagLocations = false; @@ -213,8 +238,7 @@ std::string printUsingNamespaceName(const ASTContext &Ctx, std::string Name; llvm::raw_string_ostream Out(Name); - if (auto *Qual = D.getQualifier()) - Qual->print(Out, PP); + D.getQualifier().print(Out, PP); D.getNominatedNamespaceAsWritten()->printName(Out); return Out.str(); } @@ -229,8 +253,7 @@ std::string printName(const ASTContext &Ctx, const NamedDecl &ND) { // Handle 'using namespace'. They all have the same name - <using-directive>. if (auto *UD = llvm::dyn_cast<UsingDirectiveDecl>(&ND)) { Out << "using namespace "; - if (auto *Qual = UD->getQualifier()) - Qual->print(Out, PP); + UD->getQualifier().print(Out, PP); UD->getNominatedNamespaceAsWritten()->printName(Out); return Out.str(); } @@ -250,8 +273,7 @@ std::string printName(const ASTContext &Ctx, const NamedDecl &ND) { } // Print nested name qualifier if it was written in the source code. - if (auto *Qualifier = getQualifierLoc(ND).getNestedNameSpecifier()) - Qualifier->print(Out, PP); + getQualifierLoc(ND).getNestedNameSpecifier().print(Out, PP); // Print the name itself. ND.getDeclName().print(Out, PP); // Print template arguments. @@ -391,12 +413,13 @@ preferredIncludeDirective(llvm::StringRef FileName, const LangOptions &LangOpts, } std::string printType(const QualType QT, const DeclContext &CurContext, - const llvm::StringRef Placeholder) { + const llvm::StringRef Placeholder, bool FullyQualify) { std::string Result; llvm::raw_string_ostream OS(Result); PrintingPolicy PP(CurContext.getParentASTContext().getPrintingPolicy()); PP.SuppressTagKeyword = true; PP.SuppressUnwrittenScope = true; + PP.FullyQualifiedName = FullyQualify; class PrintCB : public PrintingCallbacks { public: @@ -439,6 +462,7 @@ QualType declaredType(const TypeDecl *D) { if (const auto *CTSD = llvm::dyn_cast<ClassTemplateSpecializationDecl>(D)) if (const auto *Args = CTSD->getTemplateArgsAsWritten()) return Context.getTemplateSpecializationType( + ElaboratedTypeKeyword::None, TemplateName(CTSD->getSpecializedTemplate()), Args->arguments(), /*CanonicalArgs=*/{}); return Context.getTypeDeclType(D); @@ -664,13 +688,10 @@ std::string getQualification(ASTContext &Context, auto VisibleNamespaceDecls = getUsingNamespaceDirectives(DestContext, InsertionPoint); return getQualification( - Context, DestContext, ND->getDeclContext(), - [&](NestedNameSpecifier *NNS) { - const NamespaceDecl *NS = - dyn_cast_if_present<NamespaceDecl>(NNS->getAsNamespace()); - if (!NS) + Context, DestContext, ND->getDeclContext(), [&](const Decl *D) { + if (D->getKind() != Decl::Namespace) return false; - NS = NS->getCanonicalDecl(); + const auto *NS = cast<NamespaceDecl>(D)->getCanonicalDecl(); return llvm::any_of(VisibleNamespaceDecls, [NS](const NamespaceDecl *NSD) { return NSD->getCanonicalDecl() == NS; @@ -687,12 +708,11 @@ std::string getQualification(ASTContext &Context, (void)NS; } return getQualification( - Context, DestContext, ND->getDeclContext(), - [&](NestedNameSpecifier *NNS) { + Context, DestContext, ND->getDeclContext(), [&](const Decl *D) { return llvm::any_of(VisibleNamespaces, [&](llvm::StringRef Namespace) { std::string NS; llvm::raw_string_ostream OS(NS); - NNS->print(OS, Context.getPrintingPolicy()); + D->print(OS, Context.getPrintingPolicy()); return OS.str() == Namespace; }); }); @@ -965,7 +985,7 @@ resolveForwardingParameters(const FunctionDecl *D, unsigned MaxDepth) { // Recurse on pack parameters size_t Depth = 0; const FunctionDecl *CurrentFunction = D; - llvm::SmallSet<const FunctionTemplateDecl *, 4> SeenTemplates; + llvm::SmallPtrSet<const FunctionTemplateDecl *, 4> SeenTemplates; if (const auto *Template = D->getPrimaryTemplate()) { SeenTemplates.insert(Template); } diff --git a/clang-tools-extra/clangd/AST.h b/clang-tools-extra/clangd/AST.h index fb0722d..1538d12 100644 --- a/clang-tools-extra/clangd/AST.h +++ b/clang-tools-extra/clangd/AST.h @@ -135,7 +135,8 @@ preferredIncludeDirective(llvm::StringRef FileName, const LangOptions &LangOpts, /// Returns a QualType as string. The result doesn't contain unwritten scopes /// like anonymous/inline namespace. std::string printType(const QualType QT, const DeclContext &CurContext, - llvm::StringRef Placeholder = ""); + llvm::StringRef Placeholder = "", + bool FullyQualify = false); /// Indicates if \p D is a template instantiation implicitly generated by the /// compiler, e.g. diff --git a/clang-tools-extra/clangd/CMakeLists.txt b/clang-tools-extra/clangd/CMakeLists.txt index a1e9da4..fb3f053 100644 --- a/clang-tools-extra/clangd/CMakeLists.txt +++ b/clang-tools-extra/clangd/CMakeLists.txt @@ -6,7 +6,7 @@ add_subdirectory(support) # Configure the Features.inc file. if (NOT DEFINED CLANGD_BUILD_XPC) - if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + if("${CMAKE_SYSTEM_NAME}" MATCHES "Darwin") set(CLANGD_BUILD_XPC_DEFAULT ON) else () set(CLANGD_BUILD_XPC_DEFAULT OFF) @@ -108,6 +108,7 @@ add_clang_library(clangDaemon STATIC SemanticHighlighting.cpp SemanticSelection.cpp SourceCode.cpp + SymbolDocumentation.cpp SystemIncludeExtractor.cpp TidyProvider.cpp TUScheduler.cpp @@ -192,7 +193,7 @@ if(CLANGD_TIDY_CHECKS) endif() add_subdirectory(refactor/tweaks) -if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") +if ("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux") # FIXME: Make fuzzer not use linux-specific APIs, build it everywhere. add_subdirectory(fuzzer) endif() diff --git a/clang-tools-extra/clangd/CodeComplete.cpp b/clang-tools-extra/clangd/CodeComplete.cpp index 9c17b4c..c6deed3 100644 --- a/clang-tools-extra/clangd/CodeComplete.cpp +++ b/clang-tools-extra/clangd/CodeComplete.cpp @@ -97,6 +97,9 @@ toCompletionItemKind(index::SymbolKind Kind, const llvm::StringRef *Signature = nullptr) { using SK = index::SymbolKind; switch (Kind) { + // FIXME: for backwards compatibility, the include directive kind is treated + // the same as Unknown + case SK::IncludeDirective: case SK::Unknown: return CompletionItemKind::Missing; case SK::Module: @@ -1466,19 +1469,15 @@ bool allowIndex(CodeCompletionContext &CC) { auto Scope = CC.getCXXScopeSpecifier(); if (!Scope) return true; - NestedNameSpecifier *NameSpec = (*Scope)->getScopeRep(); - if (!NameSpec) - return true; // We only query the index when qualifier is a namespace. // If it's a class, we rely solely on sema completions. - switch (NameSpec->getKind()) { - case NestedNameSpecifier::Global: - case NestedNameSpecifier::Namespace: + switch ((*Scope)->getScopeRep().getKind()) { + case NestedNameSpecifier::Kind::Null: + case NestedNameSpecifier::Kind::Global: + case NestedNameSpecifier::Kind::Namespace: return true; - case NestedNameSpecifier::Super: - case NestedNameSpecifier::TypeSpec: - // Unresolved inside a template. - case NestedNameSpecifier::Identifier: + case NestedNameSpecifier::Kind::MicrosoftSuper: + case NestedNameSpecifier::Kind::Type: return false; } llvm_unreachable("invalid NestedNameSpecifier kind"); @@ -2434,6 +2433,9 @@ CompletionItem CodeCompletion::render(const CodeCompleteOptions &Opts) const { } llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const CodeCompletion &C) { + OS << "Signature: " << "\"" << C.Signature << "\", " + << "SnippetSuffix: " << "\"" << C.SnippetSuffix << "\"" + << ", Rendered:"; // For now just lean on CompletionItem. return OS << C.render(CodeCompleteOptions()); } diff --git a/clang-tools-extra/clangd/CodeCompletionStrings.cpp b/clang-tools-extra/clangd/CodeCompletionStrings.cpp index 9b4442b..9c4241b 100644 --- a/clang-tools-extra/clangd/CodeCompletionStrings.cpp +++ b/clang-tools-extra/clangd/CodeCompletionStrings.cpp @@ -7,13 +7,18 @@ //===----------------------------------------------------------------------===// #include "CodeCompletionStrings.h" +#include "Config.h" +#include "SymbolDocumentation.h" #include "clang-c/Index.h" #include "clang/AST/ASTContext.h" +#include "clang/AST/Comment.h" +#include "clang/AST/Decl.h" #include "clang/AST/RawCommentList.h" #include "clang/Basic/SourceManager.h" #include "clang/Sema/CodeCompleteConsumer.h" #include "llvm/Support/Compiler.h" #include "llvm/Support/JSON.h" +#include "llvm/Support/raw_ostream.h" #include <limits> #include <utility> @@ -100,16 +105,55 @@ std::string getDeclComment(const ASTContext &Ctx, const NamedDecl &Decl) { // the comments for namespaces. return ""; } - const RawComment *RC = getCompletionComment(Ctx, &Decl); - if (!RC) - return ""; - // Sanity check that the comment does not come from the PCH. We choose to not - // write them into PCH, because they are racy and slow to load. - assert(!Ctx.getSourceManager().isLoadedSourceLocation(RC->getBeginLoc())); - std::string Doc = - RC->getFormattedText(Ctx.getSourceManager(), Ctx.getDiagnostics()); - if (!looksLikeDocComment(Doc)) - return ""; + + const RawComment *RC = nullptr; + const Config &Cfg = Config::current(); + + std::string Doc; + + if (Cfg.Documentation.CommentFormat == Config::CommentFormatPolicy::Doxygen && + isa<ParmVarDecl, TemplateTypeParmDecl>(Decl)) { + // Parameters are documented in their declaration context (function or + // template function). + const NamedDecl *ND = dyn_cast<NamedDecl>(Decl.getDeclContext()); + if (!ND) + return ""; + + RC = getCompletionComment(Ctx, ND); + if (!RC) + return ""; + + // Sanity check that the comment does not come from the PCH. We choose to + // not write them into PCH, because they are racy and slow to load. + assert(!Ctx.getSourceManager().isLoadedSourceLocation(RC->getBeginLoc())); + + comments::FullComment *FC = RC->parse(Ctx, /*PP=*/nullptr, ND); + if (!FC) + return ""; + + SymbolDocCommentVisitor V(FC, Ctx.getLangOpts().CommentOpts); + std::string RawDoc; + llvm::raw_string_ostream OS(RawDoc); + + if (auto *PVD = dyn_cast<ParmVarDecl>(&Decl)) + V.parameterDocToString(PVD->getName(), OS); + else + V.templateTypeParmDocToString( + cast<TemplateTypeParmDecl>(&Decl)->getName(), OS); + + Doc = StringRef(RawDoc).trim().str(); + } else { + RC = getCompletionComment(Ctx, &Decl); + if (!RC) + return ""; + // Sanity check that the comment does not come from the PCH. We choose to + // not write them into PCH, because they are racy and slow to load. + assert(!Ctx.getSourceManager().isLoadedSourceLocation(RC->getBeginLoc())); + Doc = RC->getFormattedText(Ctx.getSourceManager(), Ctx.getDiagnostics()); + if (!looksLikeDocComment(Doc)) + return ""; + } + // Clang requires source to be UTF-8, but doesn't enforce this in comments. if (!llvm::json::isUTF8(Doc)) Doc = llvm::json::fixUTF8(Doc); diff --git a/clang-tools-extra/clangd/Config.h b/clang-tools-extra/clangd/Config.h index 2e3e0a4..01997ce 100644 --- a/clang-tools-extra/clangd/Config.h +++ b/clang-tools-extra/clangd/Config.h @@ -174,6 +174,9 @@ struct Config { struct { /// Whether hover show a.k.a type. bool ShowAKA = true; + /// Limit the number of characters returned when hovering a macro; + /// 0 is no limit. + uint32_t MacroContentsLimit = 2048; } Hover; struct { diff --git a/clang-tools-extra/clangd/ConfigCompile.cpp b/clang-tools-extra/clangd/ConfigCompile.cpp index 5dda6dd..962a48b 100644 --- a/clang-tools-extra/clangd/ConfigCompile.cpp +++ b/clang-tools-extra/clangd/ConfigCompile.cpp @@ -727,6 +727,12 @@ struct FragmentCompiler { C.Hover.ShowAKA = ShowAKA; }); } + if (F.MacroContentsLimit) { + Out.Apply.push_back( + [Limit(**F.MacroContentsLimit)](const Params &, Config &C) { + C.Hover.MacroContentsLimit = Limit; + }); + } } void compile(Fragment::InlayHintsBlock &&F) { diff --git a/clang-tools-extra/clangd/ConfigFragment.h b/clang-tools-extra/clangd/ConfigFragment.h index 0f11f37..2afeb36 100644 --- a/clang-tools-extra/clangd/ConfigFragment.h +++ b/clang-tools-extra/clangd/ConfigFragment.h @@ -361,6 +361,8 @@ struct Fragment { struct HoverBlock { /// Whether hover show a.k.a type. std::optional<Located<bool>> ShowAKA; + /// Limit the number of characters returned when hovering a macro. + std::optional<Located<uint32_t>> MacroContentsLimit; }; HoverBlock Hover; diff --git a/clang-tools-extra/clangd/ConfigYAML.cpp b/clang-tools-extra/clangd/ConfigYAML.cpp index 289b7e8..392cf19 100644 --- a/clang-tools-extra/clangd/ConfigYAML.cpp +++ b/clang-tools-extra/clangd/ConfigYAML.cpp @@ -264,6 +264,10 @@ private: if (auto ShowAKA = boolValue(N, "ShowAKA")) F.ShowAKA = *ShowAKA; }); + Dict.handle("MacroContentsLimit", [&](Node &N) { + if (auto MacroContentsLimit = uint32Value(N, "MacroContentsLimit")) + F.MacroContentsLimit = *MacroContentsLimit; + }); Dict.parse(N); } diff --git a/clang-tools-extra/clangd/DumpAST.cpp b/clang-tools-extra/clangd/DumpAST.cpp index c6075e7..9a8d41d 100644 --- a/clang-tools-extra/clangd/DumpAST.cpp +++ b/clang-tools-extra/clangd/DumpAST.cpp @@ -147,17 +147,17 @@ class DumpVisitor : public RecursiveASTVisitor<DumpVisitor> { } llvm_unreachable("Unhandled ArgKind enum"); } - std::string getKind(const NestedNameSpecifierLoc &NNSL) { - assert(NNSL.getNestedNameSpecifier()); - switch (NNSL.getNestedNameSpecifier()->getKind()) { + std::string getKind(NestedNameSpecifierLoc NNSL) { + switch (NNSL.getNestedNameSpecifier().getKind()) { + case NestedNameSpecifier::Kind::Null: + llvm_unreachable("unexpected null nested name specifier"); #define NNS_KIND(X) \ - case NestedNameSpecifier::X: \ + case NestedNameSpecifier::Kind::X: \ return #X - NNS_KIND(Identifier); NNS_KIND(Namespace); - NNS_KIND(TypeSpec); + NNS_KIND(Type); NNS_KIND(Global); - NNS_KIND(Super); + NNS_KIND(MicrosoftSuper); #undef NNS_KIND } llvm_unreachable("Unhandled SpecifierKind enum"); @@ -261,7 +261,7 @@ class DumpVisitor : public RecursiveASTVisitor<DumpVisitor> { return TL.getType().getLocalQualifiers().getAsString( Ctx.getPrintingPolicy()); if (const auto *TT = dyn_cast<TagType>(TL.getTypePtr())) - return getDetail(TT->getDecl()); + return getDetail(TT->getOriginalDecl()); if (const auto *DT = dyn_cast<DeducedType>(TL.getTypePtr())) if (DT->isDeduced()) return DT->getDeducedType().getAsString(Ctx.getPrintingPolicy()); @@ -273,16 +273,11 @@ class DumpVisitor : public RecursiveASTVisitor<DumpVisitor> { return getDetail(TT->getDecl()); return ""; } - std::string getDetail(const NestedNameSpecifierLoc &NNSL) { - const auto &NNS = *NNSL.getNestedNameSpecifier(); - switch (NNS.getKind()) { - case NestedNameSpecifier::Identifier: - return NNS.getAsIdentifier()->getName().str() + "::"; - case NestedNameSpecifier::Namespace: - return NNS.getAsNamespace()->getNameAsString() + "::"; - default: + std::string getDetail(NestedNameSpecifierLoc NNSL) { + NestedNameSpecifier NNS = NNSL.getNestedNameSpecifier(); + if (NNS.getKind() != NestedNameSpecifier::Kind::Namespace) return ""; - } + return NNS.getAsNamespaceAndPrefix().Namespace->getNameAsString() + "::"; } std::string getDetail(const CXXCtorInitializer *CCI) { if (FieldDecl *FD = CCI->getAnyMember()) @@ -346,8 +341,10 @@ public: return !D || isInjectedClassName(D) || traverseNode("declaration", D, [&] { Base::TraverseDecl(D); }); } - bool TraverseTypeLoc(TypeLoc TL) { - return !TL || traverseNode("type", TL, [&] { Base::TraverseTypeLoc(TL); }); + bool TraverseTypeLoc(TypeLoc TL, bool TraverseQualifier = true) { + return !TL || traverseNode("type", TL, [&] { + Base::TraverseTypeLoc(TL, TraverseQualifier); + }); } bool TraverseTemplateName(const TemplateName &TN) { return traverseNode("template name", TN, @@ -389,11 +386,11 @@ public: // This means we'd never see 'int' in 'const int'! Work around that here. // (The reason for the behavior is to avoid traversing the nested Type twice, // but we ignore TraverseType anyway). - bool TraverseQualifiedTypeLoc(QualifiedTypeLoc QTL) { + bool TraverseQualifiedTypeLoc(QualifiedTypeLoc QTL, bool TraverseQualifier) { return TraverseTypeLoc(QTL.getUnqualifiedLoc()); } // Uninteresting parts of the AST that don't have locations within them. - bool TraverseNestedNameSpecifier(NestedNameSpecifier *) { return true; } + bool TraverseNestedNameSpecifier(NestedNameSpecifier) { return true; } bool TraverseType(QualType) { return true; } // OpaqueValueExpr blocks traversal, we must explicitly traverse it. @@ -420,7 +417,7 @@ ASTNode dumpAST(const DynTypedNode &N, const syntax::TokenBuffer &Tokens, V.TraverseNestedNameSpecifierLoc( *const_cast<NestedNameSpecifierLoc *>(NNSL)); else if (const auto *NNS = N.get<NestedNameSpecifier>()) - V.TraverseNestedNameSpecifier(const_cast<NestedNameSpecifier *>(NNS)); + V.TraverseNestedNameSpecifier(*NNS); else if (const auto *TL = N.get<TypeLoc>()) V.TraverseTypeLoc(*const_cast<TypeLoc *>(TL)); else if (const auto *QT = N.get<QualType>()) diff --git a/clang-tools-extra/clangd/FindTarget.cpp b/clang-tools-extra/clangd/FindTarget.cpp index b108957..32018d1 100644 --- a/clang-tools-extra/clangd/FindTarget.cpp +++ b/clang-tools-extra/clangd/FindTarget.cpp @@ -366,19 +366,11 @@ public: Visitor(TargetFinder &Outer, RelSet Flags) : Outer(Outer), Flags(Flags) {} void VisitTagType(const TagType *TT) { - Outer.add(TT->getAsTagDecl(), Flags); - } - - void VisitElaboratedType(const ElaboratedType *ET) { - Outer.add(ET->desugar(), Flags); + Outer.add(cast<TagType>(TT)->getOriginalDecl(), Flags); } void VisitUsingType(const UsingType *ET) { - Outer.add(ET->getFoundDecl(), Flags); - } - - void VisitInjectedClassNameType(const InjectedClassNameType *ICNT) { - Outer.add(ICNT->getDecl(), Flags); + Outer.add(ET->getDecl(), Flags); } void VisitDecltypeType(const DecltypeType *DTT) { @@ -483,30 +475,27 @@ public: Visitor(*this, Flags).Visit(T.getTypePtr()); } - void add(const NestedNameSpecifier *NNS, RelSet Flags) { + void add(NestedNameSpecifier NNS, RelSet Flags) { if (!NNS) return; - debug(*NNS, Flags); - switch (NNS->getKind()) { - case NestedNameSpecifier::Namespace: - add(NNS->getAsNamespace(), Flags); - return; - case NestedNameSpecifier::Identifier: - if (Resolver) { - add(Resolver->resolveNestedNameSpecifierToType(NNS), Flags); - } + debug(NNS, Flags); + switch (NNS.getKind()) { + case NestedNameSpecifier::Kind::Namespace: + add(NNS.getAsNamespaceAndPrefix().Namespace, Flags); return; - case NestedNameSpecifier::TypeSpec: - add(QualType(NNS->getAsType(), 0), Flags); + case NestedNameSpecifier::Kind::Type: + add(QualType(NNS.getAsType(), 0), Flags); return; - case NestedNameSpecifier::Global: + case NestedNameSpecifier::Kind::Global: // This should be TUDecl, but we can't get a pointer to it! return; - case NestedNameSpecifier::Super: - add(NNS->getAsRecordDecl(), Flags); + case NestedNameSpecifier::Kind::MicrosoftSuper: + add(NNS.getAsMicrosoftSuper(), Flags); return; + case NestedNameSpecifier::Kind::Null: + llvm_unreachable("unexpected null nested name specifier"); } - llvm_unreachable("unhandled NestedNameSpecifier::SpecifierKind"); + llvm_unreachable("unhandled NestedNameSpecifier::Kind"); } void add(const CXXCtorInitializer *CCI, RelSet Flags) { @@ -555,7 +544,7 @@ allTargetDecls(const DynTypedNode &N, const HeuristicResolver *Resolver) { else if (const NestedNameSpecifierLoc *NNSL = N.get<NestedNameSpecifierLoc>()) Finder.add(NNSL->getNestedNameSpecifier(), Flags); else if (const NestedNameSpecifier *NNS = N.get<NestedNameSpecifier>()) - Finder.add(NNS, Flags); + Finder.add(*NNS, Flags); else if (const TypeLoc *TL = N.get<TypeLoc>()) Finder.add(TL->getType(), Flags); else if (const QualType *QT = N.get<QualType>()) @@ -861,32 +850,25 @@ refInTypeLoc(TypeLoc L, const HeuristicResolver *Resolver) { const HeuristicResolver *Resolver; llvm::SmallVector<ReferenceLoc> Refs; - void VisitElaboratedTypeLoc(ElaboratedTypeLoc L) { - // We only know about qualifier, rest if filled by inner locations. - size_t InitialSize = Refs.size(); - Visit(L.getNamedTypeLoc().getUnqualifiedLoc()); - size_t NewSize = Refs.size(); - // Add qualifier for the newly-added refs. - for (unsigned I = InitialSize; I < NewSize; ++I) { - ReferenceLoc *Ref = &Refs[I]; - // Fill in the qualifier. - assert(!Ref->Qualifier.hasQualifier() && "qualifier already set"); - Ref->Qualifier = L.getQualifierLoc(); - } + void VisitUnresolvedUsingTypeLoc(UnresolvedUsingTypeLoc L) { + Refs.push_back(ReferenceLoc{L.getQualifierLoc(), + L.getLocalSourceRange().getBegin(), + /*IsDecl=*/false, + {L.getDecl()}}); } void VisitUsingTypeLoc(UsingTypeLoc L) { - Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(), + Refs.push_back(ReferenceLoc{L.getQualifierLoc(), L.getLocalSourceRange().getBegin(), /*IsDecl=*/false, - {L.getFoundDecl()}}); + {L.getDecl()}}); } void VisitTagTypeLoc(TagTypeLoc L) { - Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(), + Refs.push_back(ReferenceLoc{L.getQualifierLoc(), L.getNameLoc(), /*IsDecl=*/false, - {L.getDecl()}}); + {L.getOriginalDecl()}}); } void VisitTemplateTypeParmTypeLoc(TemplateTypeParmTypeLoc L) { @@ -906,25 +888,18 @@ refInTypeLoc(TypeLoc L, const HeuristicResolver *Resolver) { // 2. 'vector<int>' with mask 'Underlying'. // we want to return only #1 in this case. Refs.push_back(ReferenceLoc{ - NestedNameSpecifierLoc(), L.getTemplateNameLoc(), /*IsDecl=*/false, + L.getQualifierLoc(), L.getTemplateNameLoc(), /*IsDecl=*/false, explicitReferenceTargets(DynTypedNode::create(L.getType()), DeclRelation::Alias, Resolver)}); } void VisitDeducedTemplateSpecializationTypeLoc( DeducedTemplateSpecializationTypeLoc L) { Refs.push_back(ReferenceLoc{ - NestedNameSpecifierLoc(), L.getNameLoc(), /*IsDecl=*/false, + L.getQualifierLoc(), L.getNameLoc(), /*IsDecl=*/false, explicitReferenceTargets(DynTypedNode::create(L.getType()), DeclRelation::Alias, Resolver)}); } - void VisitInjectedClassNameTypeLoc(InjectedClassNameTypeLoc TL) { - Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(), - TL.getNameLoc(), - /*IsDecl=*/false, - {TL.getDecl()}}); - } - void VisitDependentTemplateSpecializationTypeLoc( DependentTemplateSpecializationTypeLoc L) { Refs.push_back( @@ -943,12 +918,12 @@ refInTypeLoc(TypeLoc L, const HeuristicResolver *Resolver) { } void VisitTypedefTypeLoc(TypedefTypeLoc L) { - if (shouldSkipTypedef(L.getTypedefNameDecl())) + if (shouldSkipTypedef(L.getDecl())) return; - Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(), + Refs.push_back(ReferenceLoc{L.getQualifierLoc(), L.getNameLoc(), /*IsDecl=*/false, - {L.getTypedefNameDecl()}}); + {L.getDecl()}}); } void VisitObjCInterfaceTypeLoc(ObjCInterfaceTypeLoc L) { @@ -980,17 +955,6 @@ public: return true; } - bool TraverseElaboratedTypeLoc(ElaboratedTypeLoc L) { - // ElaboratedTypeLoc will reports information for its inner type loc. - // Otherwise we loose information about inner types loc's qualifier. - TypeLoc Inner = L.getNamedTypeLoc().getUnqualifiedLoc(); - if (L.getBeginLoc() == Inner.getBeginLoc()) - return RecursiveASTVisitor::TraverseTypeLoc(Inner); - else - TypeLocsToSkip.insert(Inner.getBeginLoc()); - return RecursiveASTVisitor::TraverseElaboratedTypeLoc(L); - } - bool VisitStmt(Stmt *S) { visitNode(DynTypedNode::create(*S)); return true; @@ -1051,7 +1015,7 @@ public: return true; visitNode(DynTypedNode::create(L)); // Inner type is missing information about its qualifier, skip it. - if (auto TL = L.getTypeLoc()) + if (auto TL = L.getAsTypeLoc()) TypeLocsToSkip.insert(TL.getBeginLoc()); return RecursiveASTVisitor::TraverseNestedNameSpecifierLoc(L); } @@ -1092,12 +1056,21 @@ private: if (auto *S = N.get<Stmt>()) return refInStmt(S, Resolver); if (auto *NNSL = N.get<NestedNameSpecifierLoc>()) { - // (!) 'DeclRelation::Alias' ensures we do not loose namespace aliases. - return {ReferenceLoc{ - NNSL->getPrefix(), NNSL->getLocalBeginLoc(), false, - explicitReferenceTargets( - DynTypedNode::create(*NNSL->getNestedNameSpecifier()), - DeclRelation::Alias, Resolver)}}; + // (!) 'DeclRelation::Alias' ensures we do not lose namespace aliases. + NestedNameSpecifierLoc Qualifier; + SourceLocation NameLoc; + if (auto TL = NNSL->getAsTypeLoc()) { + Qualifier = TL.getPrefix(); + NameLoc = TL.getNonPrefixBeginLoc(); + } else { + Qualifier = NNSL->getAsNamespaceAndPrefix().Prefix; + NameLoc = NNSL->getLocalBeginLoc(); + } + return { + ReferenceLoc{Qualifier, NameLoc, false, + explicitReferenceTargets( + DynTypedNode::create(NNSL->getNestedNameSpecifier()), + DeclRelation::Alias, Resolver)}}; } if (const TypeLoc *TL = N.get<TypeLoc>()) return refInTypeLoc(*TL, Resolver); @@ -1210,8 +1183,8 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, ReferenceLoc R) { OS << "}"; if (R.Qualifier) { OS << ", qualifier = '"; - R.Qualifier.getNestedNameSpecifier()->print(OS, - PrintingPolicy(LangOptions())); + R.Qualifier.getNestedNameSpecifier().print(OS, + PrintingPolicy(LangOptions())); OS << "'"; } if (R.IsDecl) diff --git a/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp b/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp index 7c0eb96..c6afd0b 100644 --- a/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp +++ b/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp @@ -833,6 +833,10 @@ bool OverlayCDB::setCompileCommand(PathRef File, std::unique_ptr<ProjectModules> OverlayCDB::getProjectModules(PathRef File) const { auto MDB = DelegatingCDB::getProjectModules(File); + if (!MDB) { + log("Failed to get compilation Database for {0}", File); + return {}; + } MDB->setCommandMangler([&Mangler = Mangler](tooling::CompileCommand &Command, PathRef CommandPath) { Mangler(Command, CommandPath); diff --git a/clang-tools-extra/clangd/Hover.cpp b/clang-tools-extra/clangd/Hover.cpp index 1e0718d..9eec322 100644 --- a/clang-tools-extra/clangd/Hover.cpp +++ b/clang-tools-extra/clangd/Hover.cpp @@ -18,6 +18,7 @@ #include "Protocol.h" #include "Selection.h" #include "SourceCode.h" +#include "SymbolDocumentation.h" #include "clang-include-cleaner/Analysis.h" #include "clang-include-cleaner/IncludeSpeller.h" #include "clang-include-cleaner/Types.h" @@ -41,6 +42,7 @@ #include "clang/AST/Type.h" #include "clang/Basic/CharInfo.h" #include "clang/Basic/LLVM.h" +#include "clang/Basic/LangOptions.h" #include "clang/Basic/SourceLocation.h" #include "clang/Basic/SourceManager.h" #include "clang/Basic/Specifiers.h" @@ -170,13 +172,14 @@ HoverInfo::PrintedType printType(QualType QT, ASTContext &ASTCtx, QT = QT->castAs<DecltypeType>()->getUnderlyingType(); HoverInfo::PrintedType Result; llvm::raw_string_ostream OS(Result.Type); - // Special case: if the outer type is a tag type without qualifiers, then - // include the tag for extra clarity. - // This isn't very idiomatic, so don't attempt it for complex cases, including - // pointers/references, template specializations, etc. + // Special case: if the outer type is a canonical tag type, then include the + // tag for extra clarity. This isn't very idiomatic, so don't attempt it for + // complex cases, including pointers/references, template specializations, + // etc. if (!QT.isNull() && !QT.hasQualifiers() && PP.SuppressTagKeyword) { - if (auto *TT = llvm::dyn_cast<TagType>(QT.getTypePtr())) - OS << TT->getDecl()->getKindName() << " "; + if (auto *TT = llvm::dyn_cast<TagType>(QT.getTypePtr()); + TT && TT->isCanonicalUnqualified()) + OS << TT->getOriginalDecl()->getKindName() << " "; } QT.print(OS, PP); @@ -451,8 +454,7 @@ std::optional<std::string> printExprValue(const Expr *E, Constant.Val.getInt().getSignificantBits() <= 64) { // Compare to int64_t to avoid bit-width match requirements. int64_t Val = Constant.Val.getInt().getExtValue(); - for (const EnumConstantDecl *ECD : - T->castAs<EnumType>()->getDecl()->enumerators()) + for (const EnumConstantDecl *ECD : T->castAsEnumDecl()->enumerators()) if (ECD->getInitVal() == Val) return llvm::formatv("{0} ({1})", ECD->getNameAsString(), printHex(Constant.Val.getInt())) @@ -627,6 +629,9 @@ HoverInfo getHoverContents(const NamedDecl *D, const PrintingPolicy &PP, HI.Name = printName(Ctx, *D); const auto *CommentD = getDeclForComment(D); HI.Documentation = getDeclComment(Ctx, *CommentD); + // save the language options to be able to create the comment::CommandTraits + // to parse the documentation + HI.CommentOpts = D->getASTContext().getLangOpts().CommentOpts; enhanceFromIndex(HI, *CommentD, Index); if (HI.Documentation.empty()) HI.Documentation = synthesizeDocumentation(D); @@ -789,7 +794,9 @@ HoverInfo getHoverContents(const DefinedMacro &Macro, const syntax::Token &Tok, for (const auto &ExpandedTok : Expansion->Expanded) { ExpansionText += ExpandedTok.text(SM); ExpansionText += " "; - if (ExpansionText.size() > 2048) { + const Config &Cfg = Config::current(); + const size_t Limit = static_cast<size_t>(Cfg.Hover.MacroContentsLimit); + if (Limit && ExpansionText.size() > Limit) { ExpansionText.clear(); break; } @@ -826,7 +833,7 @@ std::optional<HoverInfo> getThisExprHoverContents(const CXXThisExpr *CTE, ASTContext &ASTCtx, const PrintingPolicy &PP) { QualType OriginThisType = CTE->getType()->getPointeeType(); - QualType ClassType = declaredType(OriginThisType->getAsTagDecl()); + QualType ClassType = declaredType(OriginThisType->castAsTagDecl()); // For partial specialization class, origin `this` pointee type will be // parsed as `InjectedClassNameType`, which will ouput template arguments // like "type-parameter-0-0". So we retrieve user written class type in this @@ -967,10 +974,11 @@ void addLayoutInfo(const NamedDecl &ND, HoverInfo &HI) { const auto &Ctx = ND.getASTContext(); if (auto *RD = llvm::dyn_cast<RecordDecl>(&ND)) { - if (auto Size = Ctx.getTypeSizeInCharsIfKnown(RD->getTypeForDecl())) + CanQualType RT = Ctx.getCanonicalTagType(RD); + if (auto Size = Ctx.getTypeSizeInCharsIfKnown(RT)) HI.Size = Size->getQuantity() * 8; if (!RD->isDependentType() && RD->isCompleteDefinition()) - HI.Align = Ctx.getTypeAlign(RD->getTypeForDecl()); + HI.Align = Ctx.getTypeAlign(RT); return; } @@ -1267,10 +1275,10 @@ std::optional<HoverInfo> getHover(ParsedAST &AST, Position Pos, HoverCountMetric.record(1, "include"); HoverInfo HI; HI.Name = std::string(llvm::sys::path::filename(Inc.Resolved)); - // FIXME: We don't have a fitting value for Kind. HI.Definition = URIForFile::canonicalize(Inc.Resolved, AST.tuPath()).file().str(); HI.DefinitionLanguage = ""; + HI.Kind = index::SymbolKind::IncludeDirective; maybeAddUsedSymbols(AST, HI, Inc); return HI; } @@ -1388,9 +1396,241 @@ static std::string formatOffset(uint64_t OffsetInBits) { return Offset; } -markup::Document HoverInfo::present() const { +void HoverInfo::calleeArgInfoToMarkupParagraph(markup::Paragraph &P) const { + assert(CallPassType); + std::string Buffer; + llvm::raw_string_ostream OS(Buffer); + OS << "Passed "; + if (CallPassType->PassBy != HoverInfo::PassType::Value) { + OS << "by "; + if (CallPassType->PassBy == HoverInfo::PassType::ConstRef) + OS << "const "; + OS << "reference "; + } + if (CalleeArgInfo->Name) + OS << "as " << CalleeArgInfo->Name; + else if (CallPassType->PassBy == HoverInfo::PassType::Value) + OS << "by value"; + if (CallPassType->Converted && CalleeArgInfo->Type) + OS << " (converted to " << CalleeArgInfo->Type->Type << ")"; + P.appendText(OS.str()); +} + +void HoverInfo::usedSymbolNamesToMarkup(markup::Document &Output) const { + markup::Paragraph &P = Output.addParagraph(); + P.appendText("provides "); + + const std::vector<std::string>::size_type SymbolNamesLimit = 5; + auto Front = llvm::ArrayRef(UsedSymbolNames).take_front(SymbolNamesLimit); + + llvm::interleave( + Front, [&](llvm::StringRef Sym) { P.appendCode(Sym); }, + [&] { P.appendText(", "); }); + if (UsedSymbolNames.size() > Front.size()) { + P.appendText(" and "); + P.appendText(std::to_string(UsedSymbolNames.size() - Front.size())); + P.appendText(" more"); + } +} + +void HoverInfo::providerToMarkupParagraph(markup::Document &Output) const { + markup::Paragraph &DI = Output.addParagraph(); + DI.appendText("provided by"); + DI.appendSpace(); + DI.appendCode(Provider); +} + +void HoverInfo::definitionScopeToMarkup(markup::Document &Output) const { + std::string Buffer; + + // Append scope comment, dropping trailing "::". + // Note that we don't print anything for global namespace, to not annoy + // non-c++ projects or projects that are not making use of namespaces. + if (!LocalScope.empty()) { + // Container name, e.g. class, method, function. + // We might want to propagate some info about container type to print + // function foo, class X, method X::bar, etc. + Buffer += "// In " + llvm::StringRef(LocalScope).rtrim(':').str() + '\n'; + } else if (NamespaceScope && !NamespaceScope->empty()) { + Buffer += "// In namespace " + + llvm::StringRef(*NamespaceScope).rtrim(':').str() + '\n'; + } + + if (!AccessSpecifier.empty()) { + Buffer += AccessSpecifier + ": "; + } + + Buffer += Definition; + + Output.addCodeBlock(Buffer, DefinitionLanguage); +} + +void HoverInfo::valueToMarkupParagraph(markup::Paragraph &P) const { + P.appendText("Value = "); + P.appendCode(*Value); +} + +void HoverInfo::offsetToMarkupParagraph(markup::Paragraph &P) const { + P.appendText("Offset: " + formatOffset(*Offset)); +} + +void HoverInfo::sizeToMarkupParagraph(markup::Paragraph &P) const { + P.appendText("Size: " + formatSize(*Size)); + if (Padding && *Padding != 0) { + P.appendText(llvm::formatv(" (+{0} padding)", formatSize(*Padding)).str()); + } + if (Align) + P.appendText(", alignment " + formatSize(*Align)); +} + +markup::Document HoverInfo::presentDoxygen() const { + markup::Document Output; + // Header contains a text of the form: + // variable `var` + // + // class `X` + // + // function `foo` + // + // expression + // + // Note that we are making use of a level-3 heading because VSCode renders + // level 1 and 2 headers in a huge font, see + // https://github.com/microsoft/vscode/issues/88417 for details. + markup::Paragraph &Header = Output.addHeading(3); + if (Kind != index::SymbolKind::Unknown && + Kind != index::SymbolKind::IncludeDirective) + Header.appendText(index::getSymbolKindString(Kind)).appendSpace(); + assert(!Name.empty() && "hover triggered on a nameless symbol"); + + if (Kind == index::SymbolKind::IncludeDirective) { + Header.appendCode(Name); + + if (!Definition.empty()) + Output.addParagraph().appendCode(Definition); + + if (!UsedSymbolNames.empty()) { + Output.addRuler(); + usedSymbolNamesToMarkup(Output); + } + + return Output; + } + + if (!Definition.empty()) { + Output.addRuler(); + definitionScopeToMarkup(Output); + } else { + Header.appendCode(Name); + } + + if (!Provider.empty()) { + providerToMarkupParagraph(Output); + } + + // Put a linebreak after header to increase readability. + Output.addRuler(); + + SymbolDocCommentVisitor SymbolDoc(Documentation, CommentOpts); + + if (SymbolDoc.hasBriefCommand()) { + SymbolDoc.briefToMarkup(Output.addParagraph()); + Output.addRuler(); + } + + // For functions we display signature in a list form, e.g.: + // Template Parameters: + // - `typename T` - description + // Parameters: + // - `bool param1` - description + // - `int param2 = 5` - description + // Returns + // `type` - description + if (TemplateParameters && !TemplateParameters->empty()) { + Output.addParagraph().appendBoldText("Template Parameters:"); + markup::BulletList &L = Output.addBulletList(); + for (const auto &Param : *TemplateParameters) { + markup::Paragraph &P = L.addItem().addParagraph(); + P.appendCode(llvm::to_string(Param)); + if (SymbolDoc.isTemplateTypeParmDocumented(llvm::to_string(Param.Name))) { + P.appendText(" - "); + SymbolDoc.templateTypeParmDocToMarkup(llvm::to_string(Param.Name), P); + } + } + Output.addRuler(); + } + + if (Parameters && !Parameters->empty()) { + Output.addParagraph().appendBoldText("Parameters:"); + markup::BulletList &L = Output.addBulletList(); + for (const auto &Param : *Parameters) { + markup::Paragraph &P = L.addItem().addParagraph(); + P.appendCode(llvm::to_string(Param)); + + if (SymbolDoc.isParameterDocumented(llvm::to_string(Param.Name))) { + P.appendText(" - "); + SymbolDoc.parameterDocToMarkup(llvm::to_string(Param.Name), P); + } + } + Output.addRuler(); + } + + // Print Types on their own lines to reduce chances of getting line-wrapped by + // editor, as they might be long. + if (ReturnType && + ((ReturnType->Type != "void" && !ReturnType->AKA.has_value()) || + (ReturnType->AKA.has_value() && ReturnType->AKA != "void"))) { + Output.addParagraph().appendBoldText("Returns:"); + markup::Paragraph &P = Output.addParagraph(); + P.appendCode(llvm::to_string(*ReturnType)); + + if (SymbolDoc.hasReturnCommand()) { + P.appendText(" - "); + SymbolDoc.returnToMarkup(P); + } + Output.addRuler(); + } + + // add specially handled doxygen commands. + SymbolDoc.warningsToMarkup(Output); + SymbolDoc.notesToMarkup(Output); + + // add any other documentation. + SymbolDoc.docToMarkup(Output); + + Output.addRuler(); + + // Don't print Type after Parameters or ReturnType as this will just duplicate + // the information + if (Type && !ReturnType && !Parameters) + Output.addParagraph().appendText("Type: ").appendCode( + llvm::to_string(*Type)); + if (Value) { + valueToMarkupParagraph(Output.addParagraph()); + } + + if (Offset) + offsetToMarkupParagraph(Output.addParagraph()); + if (Size) { + sizeToMarkupParagraph(Output.addParagraph()); + } + + if (CalleeArgInfo) { + calleeArgInfoToMarkupParagraph(Output.addParagraph()); + } + + if (!UsedSymbolNames.empty()) { + Output.addRuler(); + usedSymbolNamesToMarkup(Output); + } + + return Output; +} + +markup::Document HoverInfo::presentDefault() const { + markup::Document Output; // Header contains a text of the form: // variable `var` // @@ -1404,17 +1644,14 @@ markup::Document HoverInfo::present() const { // level 1 and 2 headers in a huge font, see // https://github.com/microsoft/vscode/issues/88417 for details. markup::Paragraph &Header = Output.addHeading(3); - if (Kind != index::SymbolKind::Unknown) + if (Kind != index::SymbolKind::Unknown && + Kind != index::SymbolKind::IncludeDirective) Header.appendText(index::getSymbolKindString(Kind)).appendSpace(); assert(!Name.empty() && "hover triggered on a nameless symbol"); Header.appendCode(Name); if (!Provider.empty()) { - markup::Paragraph &DI = Output.addParagraph(); - DI.appendText("provided by"); - DI.appendSpace(); - DI.appendCode(Provider); - Output.addRuler(); + providerToMarkupParagraph(Output); } // Put a linebreak after header to increase readability. @@ -1432,7 +1669,7 @@ markup::Document HoverInfo::present() const { } if (Parameters && !Parameters->empty()) { - Output.addParagraph().appendText("Parameters: "); + Output.addParagraph().appendText("Parameters:"); markup::BulletList &L = Output.addBulletList(); for (const auto &Param : *Parameters) L.addItem().addParagraph().appendCode(llvm::to_string(Param)); @@ -1445,41 +1682,17 @@ markup::Document HoverInfo::present() const { llvm::to_string(*Type)); if (Value) { - markup::Paragraph &P = Output.addParagraph(); - P.appendText("Value = "); - P.appendCode(*Value); + valueToMarkupParagraph(Output.addParagraph()); } if (Offset) - Output.addParagraph().appendText("Offset: " + formatOffset(*Offset)); + offsetToMarkupParagraph(Output.addParagraph()); if (Size) { - auto &P = Output.addParagraph().appendText("Size: " + formatSize(*Size)); - if (Padding && *Padding != 0) { - P.appendText( - llvm::formatv(" (+{0} padding)", formatSize(*Padding)).str()); - } - if (Align) - P.appendText(", alignment " + formatSize(*Align)); + sizeToMarkupParagraph(Output.addParagraph()); } if (CalleeArgInfo) { - assert(CallPassType); - std::string Buffer; - llvm::raw_string_ostream OS(Buffer); - OS << "Passed "; - if (CallPassType->PassBy != HoverInfo::PassType::Value) { - OS << "by "; - if (CallPassType->PassBy == HoverInfo::PassType::ConstRef) - OS << "const "; - OS << "reference "; - } - if (CalleeArgInfo->Name) - OS << "as " << CalleeArgInfo->Name; - else if (CallPassType->PassBy == HoverInfo::PassType::Value) - OS << "by value"; - if (CallPassType->Converted && CalleeArgInfo->Type) - OS << " (converted to " << CalleeArgInfo->Type->Type << ")"; - Output.addParagraph().appendText(OS.str()); + calleeArgInfoToMarkupParagraph(Output.addParagraph()); } if (!Documentation.empty()) @@ -1487,49 +1700,12 @@ markup::Document HoverInfo::present() const { if (!Definition.empty()) { Output.addRuler(); - std::string Buffer; - - if (!Definition.empty()) { - // Append scope comment, dropping trailing "::". - // Note that we don't print anything for global namespace, to not annoy - // non-c++ projects or projects that are not making use of namespaces. - if (!LocalScope.empty()) { - // Container name, e.g. class, method, function. - // We might want to propagate some info about container type to print - // function foo, class X, method X::bar, etc. - Buffer += - "// In " + llvm::StringRef(LocalScope).rtrim(':').str() + '\n'; - } else if (NamespaceScope && !NamespaceScope->empty()) { - Buffer += "// In namespace " + - llvm::StringRef(*NamespaceScope).rtrim(':').str() + '\n'; - } - - if (!AccessSpecifier.empty()) { - Buffer += AccessSpecifier + ": "; - } - - Buffer += Definition; - } - - Output.addCodeBlock(Buffer, DefinitionLanguage); + definitionScopeToMarkup(Output); } if (!UsedSymbolNames.empty()) { Output.addRuler(); - markup::Paragraph &P = Output.addParagraph(); - P.appendText("provides "); - - const std::vector<std::string>::size_type SymbolNamesLimit = 5; - auto Front = llvm::ArrayRef(UsedSymbolNames).take_front(SymbolNamesLimit); - - llvm::interleave( - Front, [&](llvm::StringRef Sym) { P.appendCode(Sym); }, - [&] { P.appendText(", "); }); - if (UsedSymbolNames.size() > Front.size()) { - P.appendText(" and "); - P.appendText(std::to_string(UsedSymbolNames.size() - Front.size())); - P.appendText(" more"); - } + usedSymbolNamesToMarkup(Output); } return Output; @@ -1538,21 +1714,19 @@ markup::Document HoverInfo::present() const { std::string HoverInfo::present(MarkupKind Kind) const { if (Kind == MarkupKind::Markdown) { const Config &Cfg = Config::current(); - if ((Cfg.Documentation.CommentFormat == - Config::CommentFormatPolicy::Markdown) || - (Cfg.Documentation.CommentFormat == - Config::CommentFormatPolicy::Doxygen)) - // If the user prefers Markdown, we use the present() method to generate - // the Markdown output. - return present().asMarkdown(); + if (Cfg.Documentation.CommentFormat == + Config::CommentFormatPolicy::Markdown) + return presentDefault().asMarkdown(); + if (Cfg.Documentation.CommentFormat == Config::CommentFormatPolicy::Doxygen) + return presentDoxygen().asMarkdown(); if (Cfg.Documentation.CommentFormat == Config::CommentFormatPolicy::PlainText) // If the user prefers plain text, we use the present() method to generate // the plain text output. - return present().asEscapedMarkdown(); + return presentDefault().asEscapedMarkdown(); } - return present().asPlainText(); + return presentDefault().asPlainText(); } // If the backtick at `Offset` starts a probable quoted range, return the range diff --git a/clang-tools-extra/clangd/Hover.h b/clang-tools-extra/clangd/Hover.h index 2f65431..614180a 100644 --- a/clang-tools-extra/clangd/Hover.h +++ b/clang-tools-extra/clangd/Hover.h @@ -74,6 +74,8 @@ struct HoverInfo { std::optional<Range> SymRange; index::SymbolKind Kind = index::SymbolKind::Unknown; std::string Documentation; + // required to create a comments::CommandTraits object without the ASTContext + CommentOptions CommentOpts; /// Source code containing the definition of the symbol. std::string Definition; const char *DefinitionLanguage = "cpp"; @@ -118,10 +120,23 @@ struct HoverInfo { // alphabetical order. std::vector<std::string> UsedSymbolNames; - /// Produce a user-readable information. - markup::Document present() const; - + /// Produce a user-readable information based on the specified markup kind. std::string present(MarkupKind Kind) const; + +private: + void usedSymbolNamesToMarkup(markup::Document &Output) const; + void providerToMarkupParagraph(markup::Document &Output) const; + void definitionScopeToMarkup(markup::Document &Output) const; + void calleeArgInfoToMarkupParagraph(markup::Paragraph &P) const; + void valueToMarkupParagraph(markup::Paragraph &P) const; + void offsetToMarkupParagraph(markup::Paragraph &P) const; + void sizeToMarkupParagraph(markup::Paragraph &P) const; + + /// Parse and render the hover information as Doxygen documentation. + markup::Document presentDoxygen() const; + + /// Render the hover information as a default documentation. + markup::Document presentDefault() const; }; inline bool operator==(const HoverInfo::PrintedType &LHS, diff --git a/clang-tools-extra/clangd/IncludeFixer.cpp b/clang-tools-extra/clangd/IncludeFixer.cpp index 50bc2bd..c27d960 100644 --- a/clang-tools-extra/clangd/IncludeFixer.cpp +++ b/clang-tools-extra/clangd/IncludeFixer.cpp @@ -173,7 +173,7 @@ std::vector<Fix> IncludeFixer::fix(DiagnosticsEngine::Level DiagLevel, // `enum x : int;' is not formally an incomplete type. // We may need a full definition anyway. if (auto * ET = llvm::dyn_cast<EnumType>(T)) - if (!ET->getDecl()->getDefinition()) + if (!ET->getOriginalDecl()->getDefinition()) return fixIncompleteType(*T); } } @@ -400,35 +400,35 @@ std::optional<CheapUnresolvedName> extractUnresolvedNameCheaply( CheapUnresolvedName Result; Result.Name = Unresolved.getAsString(); if (SS && SS->isNotEmpty()) { // "::" or "ns::" - if (auto *Nested = SS->getScopeRep()) { - if (Nested->getKind() == NestedNameSpecifier::Global) { - Result.ResolvedScope = ""; - } else if (const NamespaceBaseDecl *NSB = Nested->getAsNamespace()) { - if (const auto *NS = dyn_cast<NamespaceDecl>(NSB)) { - std::string SpecifiedNS = printNamespaceScope(*NS); - std::optional<std::string> Spelling = getSpelledSpecifier(*SS, SM); - - // Check the specifier spelled in the source. - // If the resolved scope doesn't end with the spelled scope, the - // resolved scope may come from a sema typo correction. For example, - // sema assumes that "clangd::" is a typo of "clang::" and uses - // "clang::" as the specified scope in: - // namespace clang { clangd::X; } - // In this case, we use the "typo" specifier as extra scope instead - // of using the scope assumed by sema. - if (!Spelling || llvm::StringRef(SpecifiedNS).ends_with(*Spelling)) { - Result.ResolvedScope = std::move(SpecifiedNS); - } else { - Result.UnresolvedScope = std::move(*Spelling); - } + NestedNameSpecifier Nested = SS->getScopeRep(); + if (Nested.getKind() == NestedNameSpecifier::Kind::Global) { + Result.ResolvedScope = ""; + } else if (Nested.getKind() == NestedNameSpecifier::Kind::Namespace) { + const NamespaceBaseDecl *NSB = Nested.getAsNamespaceAndPrefix().Namespace; + if (const auto *NS = dyn_cast<NamespaceDecl>(NSB)) { + std::string SpecifiedNS = printNamespaceScope(*NS); + std::optional<std::string> Spelling = getSpelledSpecifier(*SS, SM); + + // Check the specifier spelled in the source. + // If the resolved scope doesn't end with the spelled scope, the + // resolved scope may come from a sema typo correction. For example, + // sema assumes that "clangd::" is a typo of "clang::" and uses + // "clang::" as the specified scope in: + // namespace clang { clangd::X; } + // In this case, we use the "typo" specifier as extra scope instead + // of using the scope assumed by sema. + if (!Spelling || llvm::StringRef(SpecifiedNS).ends_with(*Spelling)) { + Result.ResolvedScope = std::move(SpecifiedNS); } else { - Result.ResolvedScope = printNamespaceScope(*cast<NamespaceAliasDecl>(NSB)->getNamespace()); + Result.UnresolvedScope = std::move(*Spelling); } } else { - // We don't fix symbols in scopes that are not top-level e.g. class - // members, as we don't collect includes for them. - return std::nullopt; + Result.ResolvedScope = printNamespaceScope(*cast<NamespaceAliasDecl>(NSB)->getNamespace()); } + } else { + // We don't fix symbols in scopes that are not top-level e.g. class + // members, as we don't collect includes for them. + return std::nullopt; } } diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp index 197c62c..cd479e1 100644 --- a/clang-tools-extra/clangd/InlayHints.cpp +++ b/clang-tools-extra/clangd/InlayHints.cpp @@ -55,18 +55,24 @@ void stripLeadingUnderscores(StringRef &Name) { Name = Name.ltrim('_'); } // getDeclForType() returns the decl responsible for Type's spelling. // This is the inverse of ASTContext::getTypeDeclType(). -template <typename Ty, typename = decltype(((Ty *)nullptr)->getDecl())> -const NamedDecl *getDeclForTypeImpl(const Ty *T) { - return T->getDecl(); -} -const NamedDecl *getDeclForTypeImpl(const void *T) { return nullptr; } const NamedDecl *getDeclForType(const Type *T) { switch (T->getTypeClass()) { -#define ABSTRACT_TYPE(TY, BASE) -#define TYPE(TY, BASE) \ - case Type::TY: \ - return getDeclForTypeImpl(llvm::cast<TY##Type>(T)); -#include "clang/AST/TypeNodes.inc" + case Type::Enum: + case Type::Record: + case Type::InjectedClassName: + return cast<TagType>(T)->getOriginalDecl(); + case Type::TemplateSpecialization: + return cast<TemplateSpecializationType>(T) + ->getTemplateName() + .getAsTemplateDecl(/*IgnoreDeduced=*/true); + case Type::Typedef: + return cast<TypedefType>(T)->getDecl(); + case Type::UnresolvedUsing: + return cast<UnresolvedUsingType>(T)->getDecl(); + case Type::Using: + return cast<UsingType>(T)->getDecl(); + default: + return nullptr; } llvm_unreachable("Unknown TypeClass enum"); } @@ -81,8 +87,6 @@ llvm::StringRef getSimpleName(const NamedDecl &D) { return getSimpleName(D.getDeclName()); } llvm::StringRef getSimpleName(QualType T) { - if (const auto *ET = llvm::dyn_cast<ElaboratedType>(T)) - return getSimpleName(ET->getNamedType()); if (const auto *BT = llvm::dyn_cast<BuiltinType>(T)) { PrintingPolicy PP(LangOptions{}); PP.adjustForCPlusPlus(); diff --git a/clang-tools-extra/clangd/ModulesBuilder.cpp b/clang-tools-extra/clangd/ModulesBuilder.cpp index 56d00c2..64f22b9 100644 --- a/clang-tools-extra/clangd/ModulesBuilder.cpp +++ b/clang-tools-extra/clangd/ModulesBuilder.cpp @@ -14,6 +14,8 @@ #include "clang/Serialization/ASTReader.h" #include "clang/Serialization/ModuleCache.h" #include "llvm/ADT/ScopeExit.h" +#include "llvm/Support/CommandLine.h" + #include <queue> namespace clang { @@ -21,6 +23,12 @@ namespace clangd { namespace { +llvm::cl::opt<bool> DebugModulesBuilder( + "debug-modules-builder", + llvm::cl::desc("Don't remove clangd's built module files for debugging. " + "Remember to remove them later after debugging."), + llvm::cl::init(false)); + // Create a path to store module files. Generally it should be: // // {TEMP_DIRS}/clangd/module_files/{hashed-file-name}-%%-%%-%%-%%-%%-%%/. @@ -122,7 +130,7 @@ struct ModuleFile { } ~ModuleFile() { - if (!ModuleFilePath.empty()) + if (!ModuleFilePath.empty() && !DebugModulesBuilder) llvm::sys::fs::remove(ModuleFilePath); } @@ -315,7 +323,7 @@ buildModuleFile(llvm::StringRef ModuleName, PathRef ModuleUnitFileName, Cmds += Arg; } - clangd::vlog("Failed to compile {0} with command: {1}.", ModuleUnitFileName, + clangd::vlog("Failed to compile {0} with command: {1}", ModuleUnitFileName, Cmds); std::string BuiltModuleFilesStr = BuiltModuleFiles.getAsString(); @@ -325,7 +333,10 @@ buildModuleFile(llvm::StringRef ModuleName, PathRef ModuleUnitFileName, return llvm::createStringError( llvm::formatv("Failed to compile {0}. Use '--log=verbose' to view " - "detailed failure reasons.", + "detailed failure reasons. It is helpful to use " + "'--debug-modules-builder' flag to keep the clangd's " + "built module files to reproduce the failure for " + "debugging. Remember to remove them after debugging.", ModuleUnitFileName)); } diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp index 2c858e2..560b8e0 100644 --- a/clang-tools-extra/clangd/Protocol.cpp +++ b/clang-tools-extra/clangd/Protocol.cpp @@ -297,6 +297,9 @@ SymbolKind adjustKindToCapability(SymbolKind Kind, SymbolKind indexSymbolKindToSymbolKind(index::SymbolKind Kind) { switch (Kind) { + // FIXME: for backwards compatibility, the include directive kind is treated + // the same as Unknown + case index::SymbolKind::IncludeDirective: case index::SymbolKind::Unknown: return SymbolKind::Variable; case index::SymbolKind::Module: diff --git a/clang-tools-extra/clangd/Quality.cpp b/clang-tools-extra/clangd/Quality.cpp index 3f630b0..dc4afe1 100644 --- a/clang-tools-extra/clangd/Quality.cpp +++ b/clang-tools-extra/clangd/Quality.cpp @@ -143,6 +143,9 @@ categorize(const index::SymbolInfo &D) { case index::SymbolKind::Parameter: case index::SymbolKind::NonTypeTemplateParm: return SymbolQualitySignals::Variable; + // FIXME: for backwards compatibility, the include directive kind is treated + // the same as Unknown + case index::SymbolKind::IncludeDirective: case index::SymbolKind::Using: case index::SymbolKind::Module: case index::SymbolKind::Unknown: diff --git a/clang-tools-extra/clangd/ScanningProjectModules.cpp b/clang-tools-extra/clangd/ScanningProjectModules.cpp index e561513..672e996 100644 --- a/clang-tools-extra/clangd/ScanningProjectModules.cpp +++ b/clang-tools-extra/clangd/ScanningProjectModules.cpp @@ -122,8 +122,17 @@ ModuleDependencyScanner::scan(PathRef FilePath, ModuleDependencyInfo Result; if (ScanningResult->Provides) { - ModuleNameToSource[ScanningResult->Provides->ModuleName] = FilePath; Result.ModuleName = ScanningResult->Provides->ModuleName; + + auto [Iter, Inserted] = ModuleNameToSource.try_emplace( + ScanningResult->Provides->ModuleName, FilePath); + + if (!Inserted && Iter->second != FilePath) { + elog("Detected multiple source files ({0}, {1}) declaring the same " + "module: '{2}'. " + "Now clangd may find the wrong source in such case.", + Iter->second, FilePath, ScanningResult->Provides->ModuleName); + } } for (auto &Required : ScanningResult->Requires) diff --git a/clang-tools-extra/clangd/Selection.cpp b/clang-tools-extra/clangd/Selection.cpp index 277cb87..06165df 100644 --- a/clang-tools-extra/clangd/Selection.cpp +++ b/clang-tools-extra/clangd/Selection.cpp @@ -62,7 +62,8 @@ void recordMetrics(const SelectionTree &S, const LangOptions &Lang) { } // Return the range covering a node and all its children. -SourceRange getSourceRange(const DynTypedNode &N) { +SourceRange getSourceRange(const DynTypedNode &N, + bool IncludeQualifier = false) { // MemberExprs to implicitly access anonymous fields should not claim any // tokens for themselves. Given: // struct A { struct { int b; }; }; @@ -80,7 +81,7 @@ SourceRange getSourceRange(const DynTypedNode &N) { ? getSourceRange(DynTypedNode::create(*ME->getBase())) : SourceRange(); } - return N.getSourceRange(); + return N.getSourceRange(IncludeQualifier); } // An IntervalSet maintains a set of disjoint subranges of an array. @@ -643,8 +644,9 @@ public: } return traverseNode(X, [&] { return Base::TraverseDecl(X); }); } - bool TraverseTypeLoc(TypeLoc X) { - return traverseNode(&X, [&] { return Base::TraverseTypeLoc(X); }); + bool TraverseTypeLoc(TypeLoc X, bool TraverseQualifier = true) { + return traverseNode( + &X, [&] { return Base::TraverseTypeLoc(X, TraverseQualifier); }); } bool TraverseTemplateArgumentLoc(const TemplateArgumentLoc &X) { return traverseNode(&X, @@ -690,7 +692,8 @@ public: // This means we'd never see 'int' in 'const int'! Work around that here. // (The reason for the behavior is to avoid traversing the nested Type twice, // but we ignore TraverseType anyway). - bool TraverseQualifiedTypeLoc(QualifiedTypeLoc QX) { + bool TraverseQualifiedTypeLoc(QualifiedTypeLoc QX, + bool TraverseQualifier = true) { return traverseNode<TypeLoc>( &QX, [&] { return TraverseTypeLoc(QX.getUnqualifiedLoc()); }); } @@ -698,7 +701,7 @@ public: return traverseNode(&PL, [&] { return Base::TraverseObjCProtocolLoc(PL); }); } // Uninteresting parts of the AST that don't have locations within them. - bool TraverseNestedNameSpecifier(NestedNameSpecifier *) { return true; } + bool TraverseNestedNameSpecifier(NestedNameSpecifier) { return true; } bool TraverseType(QualType) { return true; } // The DeclStmt for the loop variable claims to cover the whole range @@ -798,7 +801,7 @@ private: // An optimization for a common case: nodes outside macro expansions that // don't intersect the selection may be recursively skipped. bool canSafelySkipNode(const DynTypedNode &N) { - SourceRange S = getSourceRange(N); + SourceRange S = getSourceRange(N, /*IncludeQualifier=*/true); if (auto *TL = N.get<TypeLoc>()) { // FIXME: TypeLoc::getBeginLoc()/getEndLoc() are pretty fragile // heuristics. We should consider only pruning critical TypeLoc nodes, to diff --git a/clang-tools-extra/clangd/SemanticHighlighting.cpp b/clang-tools-extra/clangd/SemanticHighlighting.cpp index e6d5cf7..2b151b1 100644 --- a/clang-tools-extra/clangd/SemanticHighlighting.cpp +++ b/clang-tools-extra/clangd/SemanticHighlighting.cpp @@ -1127,21 +1127,6 @@ public: return RecursiveASTVisitor::TraverseTemplateArgumentLoc(L); } - // findExplicitReferences will walk nested-name-specifiers and - // find anything that can be resolved to a Decl. However, non-leaf - // components of nested-name-specifiers which are dependent names - // (kind "Identifier") cannot be resolved to a decl, so we visit - // them here. - bool TraverseNestedNameSpecifierLoc(NestedNameSpecifierLoc Q) { - if (NestedNameSpecifier *NNS = Q.getNestedNameSpecifier()) { - if (NNS->getKind() == NestedNameSpecifier::Identifier) - H.addToken(Q.getLocalBeginLoc(), HighlightingKind::Type) - .addModifier(HighlightingModifier::DependentName) - .addModifier(HighlightingModifier::ClassScope); - } - return RecursiveASTVisitor::TraverseNestedNameSpecifierLoc(Q); - } - private: HighlightingsBuilder &H; }; diff --git a/clang-tools-extra/clangd/SemanticSelection.cpp b/clang-tools-extra/clangd/SemanticSelection.cpp index dd7116e..3353121 100644 --- a/clang-tools-extra/clangd/SemanticSelection.cpp +++ b/clang-tools-extra/clangd/SemanticSelection.cpp @@ -175,9 +175,8 @@ llvm::Expected<std::vector<FoldingRange>> getFoldingRanges(ParsedAST &AST) { return collectFoldingRanges(SyntaxTree, TM); } -// FIXME( usaxena95): Collect PP conditional regions, includes and other code -// regions (e.g. public/private/protected sections of classes, control flow -// statement bodies). +// FIXME( usaxena95): Collect includes and other code regions (e.g. +// public/private/protected sections of classes, control flow statement bodies). // Related issue: https://github.com/clangd/clangd/issues/310 llvm::Expected<std::vector<FoldingRange>> getFoldingRanges(const std::string &Code, bool LineFoldingOnly) { @@ -186,12 +185,6 @@ getFoldingRanges(const std::string &Code, bool LineFoldingOnly) { auto DirectiveStructure = DirectiveTree::parse(OrigStream); chooseConditionalBranches(DirectiveStructure, OrigStream); - // FIXME: Provide ranges in the disabled-PP regions as well. - auto Preprocessed = DirectiveStructure.stripDirectives(OrigStream); - - auto ParseableStream = cook(Preprocessed, genericLangOpts()); - pairBrackets(ParseableStream); - std::vector<FoldingRange> Result; auto AddFoldingRange = [&](Position Start, Position End, llvm::StringLiteral Kind) { @@ -220,7 +213,32 @@ getFoldingRanges(const std::string &Code, bool LineFoldingOnly) { auto EndPosition = [&](const Token &T) { return offsetToPosition(Code, EndOffset(T)); }; + + // Preprocessor directives + auto PPRanges = pairDirectiveRanges(DirectiveStructure, OrigStream); + for (const auto &R : PPRanges) { + auto BTok = OrigStream.tokens()[R.Begin]; + auto ETok = OrigStream.tokens()[R.End]; + if (ETok.Kind == tok::eof) + continue; + if (BTok.Line >= ETok.Line) + continue; + + Position Start = EndPosition(BTok); + Position End = StartPosition(ETok); + if (LineFoldingOnly) + End.line--; + AddFoldingRange(Start, End, FoldingRange::REGION_KIND); + } + + // FIXME: Provide ranges in the disabled-PP regions as well. + auto Preprocessed = DirectiveStructure.stripDirectives(OrigStream); + + auto ParseableStream = cook(Preprocessed, genericLangOpts()); + pairBrackets(ParseableStream); + auto Tokens = ParseableStream.tokens(); + // Brackets. for (const auto &Tok : Tokens) { if (auto *Paired = Tok.pair()) { @@ -240,6 +258,7 @@ getFoldingRanges(const std::string &Code, bool LineFoldingOnly) { return OriginalToken(T).Length >= 2 && Code.substr(StartOffset(T), 2) == "/*"; }; + // Multi-line comments. for (auto *T = Tokens.begin(); T != Tokens.end();) { if (T->Kind != tok::comment) { diff --git a/clang-tools-extra/clangd/SymbolDocumentation.cpp b/clang-tools-extra/clangd/SymbolDocumentation.cpp new file mode 100644 index 0000000..9ae1ef3 --- /dev/null +++ b/clang-tools-extra/clangd/SymbolDocumentation.cpp @@ -0,0 +1,386 @@ +//===--- SymbolDocumentation.cpp ==-------------------------------*- 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 +// +//===----------------------------------------------------------------------===// + +#include "SymbolDocumentation.h" + +#include "support/Markup.h" +#include "clang/AST/Comment.h" +#include "clang/AST/CommentCommandTraits.h" +#include "clang/AST/CommentVisitor.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/StringRef.h" + +namespace clang { +namespace clangd { +namespace { + +std::string commandMarkerAsString(comments::CommandMarkerKind CommandMarker) { + switch (CommandMarker) { + case comments::CommandMarkerKind::CMK_At: + return "@"; + case comments::CommandMarkerKind::CMK_Backslash: + return "\\"; + } + llvm_unreachable("Unknown command marker kind"); +} + +void commandToMarkup(markup::Paragraph &Out, StringRef Command, + comments::CommandMarkerKind CommandMarker, + StringRef Args) { + Out.appendBoldText(commandMarkerAsString(CommandMarker) + Command.str()); + Out.appendSpace(); + if (!Args.empty()) { + Out.appendEmphasizedText(Args.str()); + } +} +} // namespace + +class ParagraphToMarkupDocument + : public comments::ConstCommentVisitor<ParagraphToMarkupDocument> { +public: + ParagraphToMarkupDocument(markup::Paragraph &Out, + const comments::CommandTraits &Traits) + : Out(Out), Traits(Traits) {} + + void visitParagraphComment(const comments::ParagraphComment *C) { + if (!C) + return; + + for (const auto *Child = C->child_begin(); Child != C->child_end(); + ++Child) { + visit(*Child); + } + } + + void visitTextComment(const comments::TextComment *C) { + // Always trim leading space after a newline. + StringRef Text = C->getText(); + if (LastChunkEndsWithNewline && C->getText().starts_with(' ')) + Text = Text.drop_front(); + + LastChunkEndsWithNewline = C->hasTrailingNewline(); + Out.appendText(Text.str() + (LastChunkEndsWithNewline ? "\n" : "")); + } + + void visitInlineCommandComment(const comments::InlineCommandComment *C) { + + if (C->getNumArgs() > 0) { + std::string ArgText; + for (unsigned I = 0; I < C->getNumArgs(); ++I) { + if (!ArgText.empty()) + ArgText += " "; + ArgText += C->getArgText(I); + } + + switch (C->getRenderKind()) { + case comments::InlineCommandRenderKind::Monospaced: + Out.appendCode(ArgText); + break; + case comments::InlineCommandRenderKind::Bold: + Out.appendBoldText(ArgText); + break; + case comments::InlineCommandRenderKind::Emphasized: + Out.appendEmphasizedText(ArgText); + break; + default: + commandToMarkup(Out, C->getCommandName(Traits), C->getCommandMarker(), + ArgText); + break; + } + } else { + if (C->getCommandName(Traits) == "n") { + // \n is a special case, it is used to create a new line. + Out.appendText(" \n"); + LastChunkEndsWithNewline = true; + return; + } + + commandToMarkup(Out, C->getCommandName(Traits), C->getCommandMarker(), + ""); + } + } + + void visitHTMLStartTagComment(const comments::HTMLStartTagComment *STC) { + std::string TagText = "<" + STC->getTagName().str(); + + for (unsigned I = 0; I < STC->getNumAttrs(); ++I) { + const comments::HTMLStartTagComment::Attribute &Attr = STC->getAttr(I); + TagText += " " + Attr.Name.str() + "=\"" + Attr.Value.str() + "\""; + } + + if (STC->isSelfClosing()) + TagText += " /"; + TagText += ">"; + + LastChunkEndsWithNewline = STC->hasTrailingNewline(); + Out.appendText(TagText + (LastChunkEndsWithNewline ? "\n" : "")); + } + + void visitHTMLEndTagComment(const comments::HTMLEndTagComment *ETC) { + LastChunkEndsWithNewline = ETC->hasTrailingNewline(); + Out.appendText("</" + ETC->getTagName().str() + ">" + + (LastChunkEndsWithNewline ? "\n" : "")); + } + +private: + markup::Paragraph &Out; + const comments::CommandTraits &Traits; + + /// If true, the next leading space after a new line is trimmed. + /// Initially set it to true, to always trim the first text line. + bool LastChunkEndsWithNewline = true; +}; + +class ParagraphToString + : public comments::ConstCommentVisitor<ParagraphToString> { +public: + ParagraphToString(llvm::raw_string_ostream &Out, + const comments::CommandTraits &Traits) + : Out(Out), Traits(Traits) {} + + void visitParagraphComment(const comments::ParagraphComment *C) { + if (!C) + return; + + for (const auto *Child = C->child_begin(); Child != C->child_end(); + ++Child) { + visit(*Child); + } + } + + void visitTextComment(const comments::TextComment *C) { Out << C->getText(); } + + void visitInlineCommandComment(const comments::InlineCommandComment *C) { + Out << commandMarkerAsString(C->getCommandMarker()); + Out << C->getCommandName(Traits); + if (C->getNumArgs() > 0) { + for (unsigned I = 0; I < C->getNumArgs(); ++I) + Out << " " << C->getArgText(I); + } + Out << " "; + } + + void visitHTMLStartTagComment(const comments::HTMLStartTagComment *STC) { + Out << "<" << STC->getTagName().str(); + + for (unsigned I = 0; I < STC->getNumAttrs(); ++I) { + const comments::HTMLStartTagComment::Attribute &Attr = STC->getAttr(I); + Out << " " << Attr.Name.str(); + if (!Attr.Value.str().empty()) + Out << "=\"" << Attr.Value.str() << "\""; + } + + if (STC->isSelfClosing()) + Out << " /"; + Out << ">"; + + Out << (STC->hasTrailingNewline() ? "\n" : ""); + } + + void visitHTMLEndTagComment(const comments::HTMLEndTagComment *ETC) { + Out << "</" << ETC->getTagName().str() << ">" + << (ETC->hasTrailingNewline() ? "\n" : ""); + } + +private: + llvm::raw_string_ostream &Out; + const comments::CommandTraits &Traits; +}; + +class BlockCommentToMarkupDocument + : public comments::ConstCommentVisitor<BlockCommentToMarkupDocument> { +public: + BlockCommentToMarkupDocument(markup::Document &Out, + const comments::CommandTraits &Traits) + : Out(Out), Traits(Traits) {} + + void visitBlockCommandComment(const comments::BlockCommandComment *B) { + + switch (B->getCommandID()) { + case comments::CommandTraits::KCI_arg: + case comments::CommandTraits::KCI_li: + // \li and \arg are special cases, they are used to create a list item. + // In markdown it is a bullet list. + ParagraphToMarkupDocument(Out.addBulletList().addItem().addParagraph(), + Traits) + .visit(B->getParagraph()); + break; + default: { + // Some commands have arguments, like \throws. + // The arguments are not part of the paragraph. + // We need reconstruct them here. + std::string ArgText; + for (unsigned I = 0; I < B->getNumArgs(); ++I) { + if (!ArgText.empty()) + ArgText += " "; + ArgText += B->getArgText(I); + } + auto &P = Out.addParagraph(); + commandToMarkup(P, B->getCommandName(Traits), B->getCommandMarker(), + ArgText); + if (B->getParagraph() && !B->getParagraph()->isWhitespace()) { + // For commands with arguments, the paragraph starts after the first + // space. Therefore we need to append a space manually in this case. + if (!ArgText.empty()) + P.appendSpace(); + ParagraphToMarkupDocument(P, Traits).visit(B->getParagraph()); + } + } + } + } + + void visitVerbatimBlockComment(const comments::VerbatimBlockComment *VB) { + commandToMarkup(Out.addParagraph(), VB->getCommandName(Traits), + VB->getCommandMarker(), ""); + + std::string VerbatimText; + + for (const auto *LI = VB->child_begin(); LI != VB->child_end(); ++LI) { + if (const auto *Line = cast<comments::VerbatimBlockLineComment>(*LI)) { + VerbatimText += Line->getText().str() + "\n"; + } + } + + Out.addCodeBlock(VerbatimText, ""); + + commandToMarkup(Out.addParagraph(), VB->getCloseName(), + VB->getCommandMarker(), ""); + } + + void visitVerbatimLineComment(const comments::VerbatimLineComment *VL) { + auto &P = Out.addParagraph(); + commandToMarkup(P, VL->getCommandName(Traits), VL->getCommandMarker(), ""); + P.appendSpace().appendCode(VL->getText().str(), true).appendSpace(); + } + +private: + markup::Document &Out; + const comments::CommandTraits &Traits; + StringRef CommentEscapeMarker; +}; + +void SymbolDocCommentVisitor::visitBlockCommandComment( + const comments::BlockCommandComment *B) { + switch (B->getCommandID()) { + case comments::CommandTraits::KCI_brief: { + if (!BriefParagraph) { + BriefParagraph = B->getParagraph(); + return; + } + break; + } + case comments::CommandTraits::KCI_return: + case comments::CommandTraits::KCI_returns: + if (!ReturnParagraph) { + ReturnParagraph = B->getParagraph(); + return; + } + break; + case comments::CommandTraits::KCI_retval: + RetvalParagraphs.push_back(B->getParagraph()); + return; + case comments::CommandTraits::KCI_warning: + WarningParagraphs.push_back(B->getParagraph()); + return; + case comments::CommandTraits::KCI_note: + NoteParagraphs.push_back(B->getParagraph()); + return; + default: + break; + } + + // For all other commands, we store them in the UnhandledCommands map. + // This allows us to keep the order of the comments. + UnhandledCommands[CommentPartIndex] = B; + CommentPartIndex++; +} + +void SymbolDocCommentVisitor::paragraphsToMarkup( + markup::Document &Out, + const llvm::SmallVectorImpl<const comments::ParagraphComment *> &Paragraphs) + const { + if (Paragraphs.empty()) + return; + + for (const auto *P : Paragraphs) { + ParagraphToMarkupDocument(Out.addParagraph(), Traits).visit(P); + } +} + +void SymbolDocCommentVisitor::briefToMarkup(markup::Paragraph &Out) const { + if (!BriefParagraph) + return; + ParagraphToMarkupDocument(Out, Traits).visit(BriefParagraph); +} + +void SymbolDocCommentVisitor::returnToMarkup(markup::Paragraph &Out) const { + if (!ReturnParagraph) + return; + ParagraphToMarkupDocument(Out, Traits).visit(ReturnParagraph); +} + +void SymbolDocCommentVisitor::notesToMarkup(markup::Document &Out) const { + paragraphsToMarkup(Out, NoteParagraphs); +} + +void SymbolDocCommentVisitor::warningsToMarkup(markup::Document &Out) const { + paragraphsToMarkup(Out, WarningParagraphs); +} + +void SymbolDocCommentVisitor::parameterDocToMarkup( + StringRef ParamName, markup::Paragraph &Out) const { + if (ParamName.empty()) + return; + + if (const auto *P = Parameters.lookup(ParamName)) { + ParagraphToMarkupDocument(Out, Traits).visit(P->getParagraph()); + } +} + +void SymbolDocCommentVisitor::parameterDocToString( + StringRef ParamName, llvm::raw_string_ostream &Out) const { + if (ParamName.empty()) + return; + + if (const auto *P = Parameters.lookup(ParamName)) { + ParagraphToString(Out, Traits).visit(P->getParagraph()); + } +} + +void SymbolDocCommentVisitor::docToMarkup(markup::Document &Out) const { + for (unsigned I = 0; I < CommentPartIndex; ++I) { + if (const auto *BC = UnhandledCommands.lookup(I)) { + BlockCommentToMarkupDocument(Out, Traits).visit(BC); + } else if (const auto *P = FreeParagraphs.lookup(I)) { + ParagraphToMarkupDocument(Out.addParagraph(), Traits).visit(P); + } + } +} + +void SymbolDocCommentVisitor::templateTypeParmDocToMarkup( + StringRef TemplateParamName, markup::Paragraph &Out) const { + if (TemplateParamName.empty()) + return; + + if (const auto *TP = TemplateParameters.lookup(TemplateParamName)) { + ParagraphToMarkupDocument(Out, Traits).visit(TP->getParagraph()); + } +} + +void SymbolDocCommentVisitor::templateTypeParmDocToString( + StringRef TemplateParamName, llvm::raw_string_ostream &Out) const { + if (TemplateParamName.empty()) + return; + + if (const auto *P = TemplateParameters.lookup(TemplateParamName)) { + ParagraphToString(Out, Traits).visit(P->getParagraph()); + } +} + +} // namespace clangd +} // namespace clang diff --git a/clang-tools-extra/clangd/SymbolDocumentation.h b/clang-tools-extra/clangd/SymbolDocumentation.h new file mode 100644 index 0000000..b0d3428 --- /dev/null +++ b/clang-tools-extra/clangd/SymbolDocumentation.h @@ -0,0 +1,211 @@ +//===--- SymbolDocumentation.h ==---------------------------------*- 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 +// +//===----------------------------------------------------------------------===// +// +// Class to parse doxygen comments into a flat structure for consumption +// in e.g. Hover and Code Completion +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_SYMBOLDOCUMENTATION_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_SYMBOLDOCUMENTATION_H + +#include "support/Markup.h" +#include "clang/AST/Comment.h" +#include "clang/AST/CommentLexer.h" +#include "clang/AST/CommentParser.h" +#include "clang/AST/CommentSema.h" +#include "clang/AST/CommentVisitor.h" +#include "clang/Basic/SourceManager.h" +#include "llvm/Support/raw_ostream.h" +#include <string> + +namespace clang { +namespace clangd { + +class SymbolDocCommentVisitor + : public comments::ConstCommentVisitor<SymbolDocCommentVisitor> { +public: + SymbolDocCommentVisitor(comments::FullComment *FC, + const CommentOptions &CommentOpts) + : Traits(Allocator, CommentOpts), Allocator() { + if (!FC) + return; + + for (auto *Block : FC->getBlocks()) { + visit(Block); + } + } + + SymbolDocCommentVisitor(llvm::StringRef Documentation, + const CommentOptions &CommentOpts) + : Traits(Allocator, CommentOpts), Allocator() { + + if (Documentation.empty()) + return; + + CommentWithMarkers.reserve(Documentation.size() + + Documentation.count('\n') * 3); + + // The comment lexer expects doxygen markers, so add them back. + // We need to use the /// style doxygen markers because the comment could + // contain the closing the closing tag "*/" of a C Style "/** */" comment + // which would break the parsing if we would just enclose the comment text + // with "/** */". + CommentWithMarkers = "///"; + bool NewLine = true; + for (char C : Documentation) { + if (C == '\n') { + CommentWithMarkers += "\n///"; + NewLine = true; + } else { + if (NewLine && (C == '<')) { + // A comment line starting with '///<' is treated as a doxygen + // comment. Therefore add a space to separate the '<' from the comment + // marker. This allows to parse html tags at the beginning of a line + // and the escape marker prevents adding the artificial space in the + // markup documentation. The extra space will not be rendered, since + // we render it as markdown. + CommentWithMarkers += ' '; + } + CommentWithMarkers += C; + NewLine = false; + } + } + SourceManagerForFile SourceMgrForFile("mock_file.cpp", CommentWithMarkers); + + SourceManager &SourceMgr = SourceMgrForFile.get(); + // The doxygen Sema requires a Diagostics consumer, since it reports + // warnings e.g. when parameters are not documented correctly. These + // warnings are not relevant for us, so we can ignore them. + SourceMgr.getDiagnostics().setClient(new IgnoringDiagConsumer); + + comments::Sema S(Allocator, SourceMgr, SourceMgr.getDiagnostics(), Traits, + /*PP=*/nullptr); + comments::Lexer L(Allocator, SourceMgr.getDiagnostics(), Traits, + SourceMgr.getLocForStartOfFile(SourceMgr.getMainFileID()), + CommentWithMarkers.data(), + CommentWithMarkers.data() + CommentWithMarkers.size()); + comments::Parser P(L, S, Allocator, SourceMgr, SourceMgr.getDiagnostics(), + Traits); + comments::FullComment *FC = P.parseFullComment(); + + if (!FC) + return; + + for (auto *Block : FC->getBlocks()) { + visit(Block); + } + } + + bool isParameterDocumented(StringRef ParamName) const { + return Parameters.contains(ParamName); + } + + bool isTemplateTypeParmDocumented(StringRef ParamName) const { + return TemplateParameters.contains(ParamName); + } + + bool hasBriefCommand() const { return BriefParagraph; } + + bool hasReturnCommand() const { return ReturnParagraph; } + + bool hasRetvalCommands() const { return !RetvalParagraphs.empty(); } + + bool hasNoteCommands() const { return !NoteParagraphs.empty(); } + + bool hasWarningCommands() const { return !WarningParagraphs.empty(); } + + /// Converts all unhandled comment commands to a markup document. + void docToMarkup(markup::Document &Out) const; + /// Converts the "brief" command(s) to a markup document. + void briefToMarkup(markup::Paragraph &Out) const; + /// Converts the "return" command(s) to a markup document. + void returnToMarkup(markup::Paragraph &Out) const; + /// Converts the "note" command(s) to a markup document. + void notesToMarkup(markup::Document &Out) const; + /// Converts the "warning" command(s) to a markup document. + void warningsToMarkup(markup::Document &Out) const; + + void visitBlockCommandComment(const comments::BlockCommandComment *B); + + void templateTypeParmDocToMarkup(StringRef TemplateParamName, + markup::Paragraph &Out) const; + + void templateTypeParmDocToString(StringRef TemplateParamName, + llvm::raw_string_ostream &Out) const; + + void parameterDocToMarkup(StringRef ParamName, markup::Paragraph &Out) const; + + void parameterDocToString(StringRef ParamName, + llvm::raw_string_ostream &Out) const; + + void visitParagraphComment(const comments::ParagraphComment *P) { + FreeParagraphs[CommentPartIndex] = P; + CommentPartIndex++; + } + + void visitParamCommandComment(const comments::ParamCommandComment *P) { + Parameters[P->getParamNameAsWritten()] = P; + } + + void visitTParamCommandComment(const comments::TParamCommandComment *TP) { + TemplateParameters[TP->getParamNameAsWritten()] = std::move(TP); + } + +private: + comments::CommandTraits Traits; + llvm::BumpPtrAllocator Allocator; + std::string CommentWithMarkers; + + /// Index to keep track of the order of the comments. + /// We want to rearange some commands like \\param. + /// This index allows us to keep the order of the other comment parts. + unsigned CommentPartIndex = 0; + + /// Paragraph of the "brief" command. + const comments::ParagraphComment *BriefParagraph = nullptr; + + /// Paragraph of the "return" command. + const comments::ParagraphComment *ReturnParagraph = nullptr; + + /// Paragraph(s) of the "note" command(s) + llvm::SmallVector<const comments::ParagraphComment *> RetvalParagraphs; + + /// Paragraph(s) of the "note" command(s) + llvm::SmallVector<const comments::ParagraphComment *> NoteParagraphs; + + /// Paragraph(s) of the "warning" command(s) + llvm::SmallVector<const comments::ParagraphComment *> WarningParagraphs; + + /// All the paragraphs we don't have any special handling for, + /// e.g. "details". + llvm::SmallDenseMap<unsigned, const comments::BlockCommandComment *> + UnhandledCommands; + + /// Parsed paragaph(s) of the "param" comamnd(s) + llvm::SmallDenseMap<StringRef, const comments::ParamCommandComment *> + Parameters; + + /// Parsed paragaph(s) of the "tparam" comamnd(s) + llvm::SmallDenseMap<StringRef, const comments::TParamCommandComment *> + TemplateParameters; + + /// All "free" text paragraphs. + llvm::SmallDenseMap<unsigned, const comments::ParagraphComment *> + FreeParagraphs; + + void paragraphsToMarkup( + markup::Document &Out, + const llvm::SmallVectorImpl<const comments::ParagraphComment *> + &Paragraphs) const; +}; + +} // namespace clangd +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_SYMBOLDOCUMENTATION_H diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp index 83a8b72..e1c50f9 100644 --- a/clang-tools-extra/clangd/XRefs.cpp +++ b/clang-tools-extra/clangd/XRefs.cpp @@ -1106,11 +1106,11 @@ public: return true; } bool VisitBreakStmt(BreakStmt *B) { - found(Break, B->getBreakLoc()); + found(Break, B->getKwLoc()); return true; } bool VisitContinueStmt(ContinueStmt *C) { - found(Continue, C->getContinueLoc()); + found(Continue, C->getKwLoc()); return true; } bool VisitSwitchCase(SwitchCase *C) { @@ -1876,7 +1876,7 @@ static void fillSubTypes(const SymbolID &ID, }); } -using RecursionProtectionSet = llvm::SmallSet<const CXXRecordDecl *, 4>; +using RecursionProtectionSet = llvm::SmallPtrSet<const CXXRecordDecl *, 4>; // Extracts parents from AST and populates the type hierarchy item. static void fillSuperTypes(const CXXRecordDecl &CXXRD, llvm::StringRef TUPath, @@ -1965,7 +1965,8 @@ std::vector<const CXXRecordDecl *> findRecordTypeAt(ParsedAST &AST, // Return the type most associated with an AST node. // This isn't precisely defined: we want "go to type" to do something useful. -static QualType typeForNode(const SelectionTree::Node *N) { +static QualType typeForNode(const ASTContext &Ctx, + const SelectionTree::Node *N) { // If we're looking at a namespace qualifier, walk up to what it's qualifying. // (If we're pointing at a *class* inside a NNS, N will be a TypeLoc). while (N && N->ASTNode.get<NestedNameSpecifierLoc>()) @@ -1999,10 +2000,13 @@ static QualType typeForNode(const SelectionTree::Node *N) { if (const Decl *D = N->ASTNode.get<Decl>()) { struct Visitor : ConstDeclVisitor<Visitor, QualType> { + const ASTContext &Ctx; + Visitor(const ASTContext &Ctx) : Ctx(Ctx) {} + QualType VisitValueDecl(const ValueDecl *D) { return D->getType(); } // Declaration of a type => that type. QualType VisitTypeDecl(const TypeDecl *D) { - return QualType(D->getTypeForDecl(), 0); + return Ctx.getTypeDeclType(D); } // Exception: alias declaration => the underlying type, not the alias. QualType VisitTypedefNameDecl(const TypedefNameDecl *D) { @@ -2012,7 +2016,7 @@ static QualType typeForNode(const SelectionTree::Node *N) { QualType VisitTemplateDecl(const TemplateDecl *D) { return Visit(D->getTemplatedDecl()); } - } V; + } V(Ctx); return V.Visit(D); } @@ -2156,7 +2160,8 @@ std::vector<LocatedSymbol> findType(ParsedAST &AST, Position Pos, // unique_ptr<unique_ptr<T>>. Let's *not* remove them, because it gives you some // information about the type you may have not known before // (since unique_ptr<unique_ptr<T>> != unique_ptr<T>). - for (const QualType& Type : unwrapFindType(typeForNode(N), AST.getHeuristicResolver())) + for (const QualType &Type : unwrapFindType( + typeForNode(AST.getASTContext(), N), AST.getHeuristicResolver())) llvm::copy(locateSymbolForType(AST, Type, Index), std::back_inserter(LocatedSymbols)); diff --git a/clang-tools-extra/clangd/refactor/tweaks/AddUsing.cpp b/clang-tools-extra/clangd/refactor/tweaks/AddUsing.cpp index 67fc451..f65c74f 100644 --- a/clang-tools-extra/clangd/refactor/tweaks/AddUsing.cpp +++ b/clang-tools-extra/clangd/refactor/tweaks/AddUsing.cpp @@ -115,13 +115,6 @@ private: const SourceManager &SM; }; -bool isFullyQualified(const NestedNameSpecifier *NNS) { - if (!NNS) - return false; - return NNS->getKind() == NestedNameSpecifier::Global || - isFullyQualified(NNS->getPrefix()); -} - struct InsertionPointData { // Location to insert the "using" statement. If invalid then the statement // should not be inserted at all (it already exists). @@ -167,18 +160,20 @@ findInsertionPoint(const Tweak::Selection &Inputs, for (auto &U : Usings) { // Only "upgrade" to fully qualified is all relevant using decls are fully // qualified. Otherwise trust what the user typed. - if (!isFullyQualified(U->getQualifier())) + if (!U->getQualifier().isFullyQualified()) AlwaysFullyQualify = false; if (SM.isBeforeInTranslationUnit(Inputs.Cursor, U->getUsingLoc())) // "Usings" is sorted, so we're done. break; - if (const auto *Namespace = dyn_cast_if_present<NamespaceDecl>( - U->getQualifier()->getAsNamespace())) { + if (NestedNameSpecifier Qualifier = U->getQualifier(); + Qualifier.getKind() == NestedNameSpecifier::Kind::Namespace) { + const auto *Namespace = + U->getQualifier().getAsNamespaceAndPrefix().Namespace; if (Namespace->getCanonicalDecl() == QualifierToRemove.getNestedNameSpecifier() - ->getAsNamespace() - ->getCanonicalDecl() && + .getAsNamespaceAndPrefix() + .Namespace->getCanonicalDecl() && U->getName() == Name) { return InsertionPointData(); } @@ -232,8 +227,9 @@ findInsertionPoint(const Tweak::Selection &Inputs, } bool isNamespaceForbidden(const Tweak::Selection &Inputs, - const NestedNameSpecifier &Namespace) { - const auto *NS = dyn_cast<NamespaceDecl>(Namespace.getAsNamespace()); + NestedNameSpecifier Namespace) { + const auto *NS = + dyn_cast<NamespaceDecl>(Namespace.getAsNamespaceAndPrefix().Namespace); if (!NS) return true; std::string NamespaceStr = printNamespaceScope(*NS); @@ -247,11 +243,11 @@ bool isNamespaceForbidden(const Tweak::Selection &Inputs, return false; } -std::string getNNSLAsString(NestedNameSpecifierLoc &NNSL, +std::string getNNSLAsString(NestedNameSpecifierLoc NNSL, const PrintingPolicy &Policy) { std::string Out; llvm::raw_string_ostream OutStream(Out); - NNSL.getNestedNameSpecifier()->print(OutStream, Policy); + NNSL.getNestedNameSpecifier().print(OutStream, Policy); return OutStream.str(); } @@ -276,16 +272,15 @@ bool AddUsing::prepare(const Selection &Inputs) { continue; } if (auto *T = Node->ASTNode.get<TypeLoc>()) { - if (T->getAs<ElaboratedTypeLoc>()) { + // Find the outermost TypeLoc. + if (Node->Parent->ASTNode.get<NestedNameSpecifierLoc>()) + continue; + if (isa<TagType, TemplateSpecializationType, TypedefType, UsingType, + UnresolvedUsingType>(T->getTypePtr())) break; - } - if (Node->Parent->ASTNode.get<TypeLoc>() || - Node->Parent->ASTNode.get<NestedNameSpecifierLoc>()) { - // Node is TypeLoc, but it's parent is either TypeLoc or - // NestedNameSpecifier. In both cases, we want to go up, to find - // the outermost TypeLoc. + // Find the outermost TypeLoc. + if (Node->Parent->ASTNode.get<TypeLoc>()) continue; - } } break; } @@ -307,32 +302,70 @@ bool AddUsing::prepare(const Selection &Inputs) { MustInsertAfterLoc = D->getDecl()->getBeginLoc(); } } else if (auto *T = Node->ASTNode.get<TypeLoc>()) { - if (auto E = T->getAs<ElaboratedTypeLoc>()) { - QualifierToRemove = E.getQualifierLoc(); - - SpelledNameRange = E.getSourceRange(); - if (auto T = E.getNamedTypeLoc().getAs<TemplateSpecializationTypeLoc>()) { - // Remove the template arguments from the name. - SpelledNameRange.setEnd(T.getLAngleLoc().getLocWithOffset(-1)); - } - - if (const auto *ET = E.getTypePtr()) { - if (const auto *TDT = - dyn_cast<TypedefType>(ET->getNamedType().getTypePtr())) { - MustInsertAfterLoc = TDT->getDecl()->getBeginLoc(); - } else if (auto *TD = ET->getAsTagDecl()) { - MustInsertAfterLoc = TD->getBeginLoc(); - } - } + switch (T->getTypeLocClass()) { + case TypeLoc::TemplateSpecialization: { + auto TL = T->castAs<TemplateSpecializationTypeLoc>(); + QualifierToRemove = TL.getQualifierLoc(); + if (!QualifierToRemove) + break; + SpelledNameRange = TL.getTemplateNameLoc(); + if (auto *TD = TL.getTypePtr()->getTemplateName().getAsTemplateDecl( + /*IgnoreDeduced=*/true)) + MustInsertAfterLoc = TD->getBeginLoc(); + break; + } + case TypeLoc::Enum: + case TypeLoc::Record: + case TypeLoc::InjectedClassName: { + auto TL = T->castAs<TagTypeLoc>(); + QualifierToRemove = TL.getQualifierLoc(); + if (!QualifierToRemove) + break; + SpelledNameRange = TL.getNameLoc(); + MustInsertAfterLoc = TL.getOriginalDecl()->getBeginLoc(); + break; + } + case TypeLoc::Typedef: { + auto TL = T->castAs<TypedefTypeLoc>(); + QualifierToRemove = TL.getQualifierLoc(); + if (!QualifierToRemove) + break; + SpelledNameRange = TL.getNameLoc(); + MustInsertAfterLoc = TL.getDecl()->getBeginLoc(); + break; + } + case TypeLoc::UnresolvedUsing: { + auto TL = T->castAs<UnresolvedUsingTypeLoc>(); + QualifierToRemove = TL.getQualifierLoc(); + if (!QualifierToRemove) + break; + SpelledNameRange = TL.getNameLoc(); + MustInsertAfterLoc = TL.getDecl()->getBeginLoc(); + break; + } + case TypeLoc::Using: { + auto TL = T->castAs<UsingTypeLoc>(); + QualifierToRemove = TL.getQualifierLoc(); + if (!QualifierToRemove) + break; + SpelledNameRange = TL.getNameLoc(); + MustInsertAfterLoc = TL.getDecl()->getBeginLoc(); + break; + } + default: + break; } + if (QualifierToRemove) + SpelledNameRange.setBegin(QualifierToRemove.getBeginLoc()); } if (!QualifierToRemove || // FIXME: This only supports removing qualifiers that are made up of just // namespace names. If qualifier contains a type, we could take the // longest namespace prefix and remove that. - !QualifierToRemove.getNestedNameSpecifier()->getAsNamespace() || + QualifierToRemove.getNestedNameSpecifier().getKind() != + NestedNameSpecifier::Kind::Namespace || // Respect user config. - isNamespaceForbidden(Inputs, *QualifierToRemove.getNestedNameSpecifier())) + isNamespaceForbidden(Inputs, QualifierToRemove.getNestedNameSpecifier())) return false; // Macros are difficult. We only want to offer code action when what's spelled // under the cursor is a namespace qualifier. If it's a macro that expands to @@ -384,7 +417,7 @@ Expected<Tweak::Effect> AddUsing::apply(const Selection &Inputs) { llvm::raw_string_ostream UsingTextStream(UsingText); UsingTextStream << "using "; if (InsertionPoint->AlwaysFullyQualify && - !isFullyQualified(QualifierToRemove.getNestedNameSpecifier())) + !QualifierToRemove.getNestedNameSpecifier().isFullyQualified()) UsingTextStream << "::"; UsingTextStream << QualifierToSpell << SpelledName << ";" << InsertionPoint->Suffix; diff --git a/clang-tools-extra/clangd/refactor/tweaks/ExtractFunction.cpp b/clang-tools-extra/clangd/refactor/tweaks/ExtractFunction.cpp index cd07cbf..bc9a790 100644 --- a/clang-tools-extra/clangd/refactor/tweaks/ExtractFunction.cpp +++ b/clang-tools-extra/clangd/refactor/tweaks/ExtractFunction.cpp @@ -181,7 +181,7 @@ struct ExtractionZone { bool requiresHoisting(const SourceManager &SM, const HeuristicResolver *Resolver) const { // First find all the declarations that happened inside extraction zone. - llvm::SmallSet<const Decl *, 1> DeclsInExtZone; + llvm::SmallPtrSet<const Decl *, 1> DeclsInExtZone; for (auto *RootStmt : RootStmts) { findExplicitReferences( RootStmt, @@ -362,7 +362,7 @@ struct NewFunction { SourceLocation DefinitionPoint; std::optional<SourceLocation> ForwardDeclarationPoint; const CXXRecordDecl *EnclosingClass = nullptr; - const NestedNameSpecifier *DefinitionQualifier = nullptr; + NestedNameSpecifier DefinitionQualifier = std::nullopt; const DeclContext *SemanticDC = nullptr; const DeclContext *SyntacticDC = nullptr; const DeclContext *ForwardDeclarationSyntacticDC = nullptr; @@ -455,13 +455,12 @@ std::string NewFunction::renderQualifiers() const { } std::string NewFunction::renderDeclarationName(FunctionDeclKind K) const { - if (DefinitionQualifier == nullptr || K != OutOfLineDefinition) { + if (!DefinitionQualifier || K != OutOfLineDefinition) return Name; - } std::string QualifierName; llvm::raw_string_ostream Oss(QualifierName); - DefinitionQualifier->print(Oss, *LangOpts); + DefinitionQualifier.print(Oss, *LangOpts); return llvm::formatv("{0}{1}", QualifierName, Name); } diff --git a/clang-tools-extra/clangd/refactor/tweaks/PopulateSwitch.cpp b/clang-tools-extra/clangd/refactor/tweaks/PopulateSwitch.cpp index 43cfc76..2c98417 100644 --- a/clang-tools-extra/clangd/refactor/tweaks/PopulateSwitch.cpp +++ b/clang-tools-extra/clangd/refactor/tweaks/PopulateSwitch.cpp @@ -113,11 +113,11 @@ bool PopulateSwitch::prepare(const Selection &Sel) { // Ignore implicit casts, since enums implicitly cast to integer types. Cond = Cond->IgnoreParenImpCasts(); // Get the canonical type to handle typedefs. - EnumT = Cond->getType().getCanonicalType()->getAsAdjusted<EnumType>(); + EnumT = Cond->getType()->getAsCanonical<EnumType>(); if (!EnumT) return false; - EnumD = EnumT->getDecl(); - if (!EnumD || EnumD->isDependentType()) + EnumD = EnumT->getOriginalDecl()->getDefinitionOrSelf(); + if (EnumD->isDependentType()) return false; // Finally, check which cases exist and which are covered. diff --git a/clang-tools-extra/clangd/support/DirectiveTree.cpp b/clang-tools-extra/clangd/support/DirectiveTree.cpp index 7ea08ad..97b0598 100644 --- a/clang-tools-extra/clangd/support/DirectiveTree.cpp +++ b/clang-tools-extra/clangd/support/DirectiveTree.cpp @@ -356,5 +356,62 @@ TokenStream DirectiveTree::stripDirectives(const TokenStream &In) const { return Out; } +namespace { +class RangePairer { + std::vector<Token::Range> &Ranges; + +public: + RangePairer(std::vector<Token::Range> &Ranges) : Ranges(Ranges) {} + + void walk(const DirectiveTree &T) { + for (const auto &C : T.Chunks) + std::visit(*this, C); + } + + void operator()(const DirectiveTree::Code &C) {} + + void operator()(const DirectiveTree::Directive &) {} + + void operator()(const DirectiveTree::Conditional &C) { + Token::Range Range; + Token::Index Last; + auto First = true; + for (const auto &[Directive, _] : C.Branches) { + if (First) { + First = false; + } else { + Range = {Last, Directive.Tokens.Begin}; + Ranges.push_back(Range); + } + Last = Directive.Tokens.Begin; + } + + if (C.End.Kind != tok::pp_not_keyword) { + Range = {Last, C.End.Tokens.Begin}; + Ranges.push_back(Range); + } + + for (const auto &[_, SubTree] : C.Branches) + walk(SubTree); + } +}; +} // namespace + +std::vector<Token::Range> pairDirectiveRanges(const DirectiveTree &Tree, + const TokenStream &Code) { + std::vector<Token::Range> Ranges; + RangePairer(Ranges).walk(Tree); + + // Transform paired ranges to start with last token in its logical line + for (auto &R : Ranges) { + const Token *Tok = &Code.tokens()[R.Begin + 1]; + while (Tok->Kind != tok::eof && !Tok->flag(LexFlags::StartsPPLine)) + ++Tok; + Tok = Tok - 1; + R.Begin = Tok->OriginalIndex; + } + return Ranges; +} + } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/support/DirectiveTree.h b/clang-tools-extra/clangd/support/DirectiveTree.h index 34f5a88..373af32 100644 --- a/clang-tools-extra/clangd/support/DirectiveTree.h +++ b/clang-tools-extra/clangd/support/DirectiveTree.h @@ -124,6 +124,10 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &, /// The choices are stored in Conditional::Taken nodes. void chooseConditionalBranches(DirectiveTree &, const TokenStream &Code); +/// Pairs preprocessor conditional directives and computes their token ranges. +std::vector<Token::Range> pairDirectiveRanges(const DirectiveTree &Tree, + const TokenStream &Code); + } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/support/Markup.cpp b/clang-tools-extra/clangd/support/Markup.cpp index a130830..89bdc65 100644 --- a/clang-tools-extra/clangd/support/Markup.cpp +++ b/clang-tools-extra/clangd/support/Markup.cpp @@ -363,7 +363,12 @@ public: void renderMarkdown(llvm::raw_ostream &OS) const override { std::string Marker = getMarkerForCodeBlock(Contents); // No need to pad from previous blocks, as they should end with a new line. - OS << Marker << Language << '\n' << Contents << '\n' << Marker << '\n'; + OS << Marker << Language << '\n' << Contents; + if (!Contents.empty() && Contents.back() != '\n') + OS << '\n'; + // Always end with an empty line to separate code blocks from following + // paragraphs. + OS << Marker << "\n\n"; } void renderPlainText(llvm::raw_ostream &OS) const override { diff --git a/clang-tools-extra/clangd/test/modules_no_cdb.test b/clang-tools-extra/clangd/test/modules_no_cdb.test new file mode 100644 index 0000000..8f92be2 --- /dev/null +++ b/clang-tools-extra/clangd/test/modules_no_cdb.test @@ -0,0 +1,66 @@ +# A smoke test to check that clangd works without compilation database +# +# Windows have different escaping modes. +# FIXME: We should add one for windows. +# UNSUPPORTED: system-windows +# +# RUN: rm -fr %t +# RUN: mkdir -p %t +# RUN: split-file %s %t +# +# RUN: sed -e "s|DIR|%/t|g" %t/definition.jsonrpc.tmpl > %t/definition.jsonrpc +# +# RUN: clangd -experimental-modules-support -lit-test < %t/definition.jsonrpc \ +# RUN: | FileCheck -strict-whitespace %t/definition.jsonrpc + +#--- A.h +void printA(); + +#--- Use.cpp +#include "A.h" +void foo() { + print +} + +#--- definition.jsonrpc.tmpl +{ + "jsonrpc": "2.0", + "id": 0, + "method": "initialize", + "params": { + "processId": 123, + "rootPath": "clangd", + "capabilities": { + "textDocument": { + "completion": { + "completionItem": { + "snippetSupport": true + } + } + } + }, + "trace": "off" + } +} +--- +{ + "jsonrpc": "2.0", + "method": "textDocument/didOpen", + "params": { + "textDocument": { + "uri": "file://DIR/Use.cpp", + "languageId": "cpp", + "version": 1, + "text": "#include \"A.h\"\nvoid foo() {\n print\n}\n" + } + } +} + +# CHECK: "message"{{.*}}printA{{.*}}(fix available) + +--- +{"jsonrpc":"2.0","id":1,"method":"textDocument/completion","params":{"textDocument":{"uri":"file://DIR/Use.cpp"},"context":{"triggerKind":1},"position":{"line":2,"character":6}}} +--- +{"jsonrpc":"2.0","id":2,"method":"shutdown"} +--- +{"jsonrpc":"2.0","method":"exit"} diff --git a/clang-tools-extra/clangd/unittests/ASTTests.cpp b/clang-tools-extra/clangd/unittests/ASTTests.cpp index d0bc3c4..76d46ba 100644 --- a/clang-tools-extra/clangd/unittests/ASTTests.cpp +++ b/clang-tools-extra/clangd/unittests/ASTTests.cpp @@ -421,7 +421,7 @@ TEST(ClangdAST, GetQualification) { { R"cpp( namespace ns1 { namespace ns2 { void Foo(); } } - void insert(); // ns2::Foo + void insert(); // ns1::ns2::Foo namespace ns1 { void insert(); // ns2::Foo namespace ns2 { @@ -429,7 +429,7 @@ TEST(ClangdAST, GetQualification) { } } )cpp", - {"ns2::", "ns2::", ""}, + {"ns1::ns2::", "ns2::", ""}, {"ns1::"}, }, { @@ -531,7 +531,8 @@ TEST(ClangdAST, PrintType) { ASSERT_EQ(InsertionPoints.size(), Case.Types.size()); for (size_t I = 0, E = InsertionPoints.size(); I != E; ++I) { const auto *DC = InsertionPoints[I]; - EXPECT_EQ(printType(AST.getASTContext().getTypeDeclType(TargetDecl), *DC), + EXPECT_EQ(printType(AST.getASTContext().getTypeDeclType(TargetDecl), *DC, + /*Placeholder=*/"", /*FullyQualify=*/true), Case.Types[I]); } } diff --git a/clang-tools-extra/clangd/unittests/CMakeLists.txt b/clang-tools-extra/clangd/unittests/CMakeLists.txt index d425070..9656eea 100644 --- a/clang-tools-extra/clangd/unittests/CMakeLists.txt +++ b/clang-tools-extra/clangd/unittests/CMakeLists.txt @@ -92,6 +92,7 @@ add_unittest(ClangdUnitTests ClangdTests SourceCodeTests.cpp StdLibTests.cpp SymbolCollectorTests.cpp + SymbolDocumentationTests.cpp SymbolInfoTests.cpp SyncAPI.cpp TUSchedulerTests.cpp diff --git a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp index 1a1c32c..7640569 100644 --- a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp +++ b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp @@ -4473,6 +4473,198 @@ TEST(CompletionTest, SkipExplicitObjectParameter) { snippetSuffix("")))); } } + +TEST(CompletionTest, MemberAccessInExplicitObjMemfn) { + Annotations Code(R"cpp( + struct A { + int member {}; + int memberFnA(int a); + int memberFnA(this A&, float a); + + void foo(this A& self) { + // Should not offer any members here, since + // it needs to be referenced through `self`. + mem$c1^; + // should offer all results + self.mem$c2^; + + [&]() { + // should not offer any results + mem$c3^; + }(); + } + }; + )cpp"); + + auto TU = TestTU::withCode(Code.code()); + TU.ExtraArgs = {"-std=c++23"}; + + auto Preamble = TU.preamble(); + ASSERT_TRUE(Preamble); + + CodeCompleteOptions Opts{}; + + MockFS FS; + auto Inputs = TU.inputs(FS); + + { + auto Result = codeComplete(testPath(TU.Filename), Code.point("c1"), + Preamble.get(), Inputs, Opts); + + EXPECT_THAT(Result.Completions, ElementsAre()); + } + { + auto Result = codeComplete(testPath(TU.Filename), Code.point("c2"), + Preamble.get(), Inputs, Opts); + + EXPECT_THAT( + Result.Completions, + UnorderedElementsAre(named("member"), + AllOf(named("memberFnA"), signature("(int a)"), + snippetSuffix("(${1:int a})")), + AllOf(named("memberFnA"), signature("(float a)"), + snippetSuffix("(${1:float a})")))); + } + { + auto Result = codeComplete(testPath(TU.Filename), Code.point("c3"), + Preamble.get(), Inputs, Opts); + + EXPECT_THAT(Result.Completions, ElementsAre()); + } +} + +TEST(CompletionTest, ListExplicitObjectOverloads) { + Annotations Code(R"cpp( + struct S { + void foo1(int a); + void foo2(int a) const; + void foo2(this const S& self, float a); + void foo3(this const S& self, int a); + void foo4(this S& self, int a); + }; + + void S::foo1(int a) { + this->$c1^; + } + + void S::foo2(int a) const { + this->$c2^; + } + + void S::foo3(this const S& self, int a) { + self.$c3^; + } + + void S::foo4(this S& self, int a) { + self.$c4^; + } + + void test1(S s) { + s.$c5^; + } + + void test2(const S s) { + s.$c6^; + } + )cpp"); + + auto TU = TestTU::withCode(Code.code()); + TU.ExtraArgs = {"-std=c++23"}; + + auto Preamble = TU.preamble(); + ASSERT_TRUE(Preamble); + + CodeCompleteOptions Opts{}; + + MockFS FS; + auto Inputs = TU.inputs(FS); + + { + auto Result = codeComplete(testPath(TU.Filename), Code.point("c1"), + Preamble.get(), Inputs, Opts); + EXPECT_THAT( + Result.Completions, + UnorderedElementsAre(AllOf(named("foo1"), signature("(int a)"), + snippetSuffix("(${1:int a})")), + AllOf(named("foo2"), signature("(int a) const"), + snippetSuffix("(${1:int a})")), + AllOf(named("foo2"), signature("(float a) const"), + snippetSuffix("(${1:float a})")), + AllOf(named("foo3"), signature("(int a) const"), + snippetSuffix("(${1:int a})")), + AllOf(named("foo4"), signature("(int a)"), + snippetSuffix("(${1:int a})")))); + } + { + auto Result = codeComplete(testPath(TU.Filename), Code.point("c2"), + Preamble.get(), Inputs, Opts); + EXPECT_THAT( + Result.Completions, + UnorderedElementsAre(AllOf(named("foo2"), signature("(int a) const"), + snippetSuffix("(${1:int a})")), + AllOf(named("foo2"), signature("(float a) const"), + snippetSuffix("(${1:float a})")), + AllOf(named("foo3"), signature("(int a) const"), + snippetSuffix("(${1:int a})")))); + } + { + auto Result = codeComplete(testPath(TU.Filename), Code.point("c3"), + Preamble.get(), Inputs, Opts); + EXPECT_THAT( + Result.Completions, + UnorderedElementsAre(AllOf(named("foo2"), signature("(int a) const"), + snippetSuffix("(${1:int a})")), + AllOf(named("foo2"), signature("(float a) const"), + snippetSuffix("(${1:float a})")), + AllOf(named("foo3"), signature("(int a) const"), + snippetSuffix("(${1:int a})")))); + } + { + auto Result = codeComplete(testPath(TU.Filename), Code.point("c4"), + Preamble.get(), Inputs, Opts); + EXPECT_THAT( + Result.Completions, + UnorderedElementsAre(AllOf(named("foo1"), signature("(int a)"), + snippetSuffix("(${1:int a})")), + AllOf(named("foo2"), signature("(int a) const"), + snippetSuffix("(${1:int a})")), + AllOf(named("foo2"), signature("(float a) const"), + snippetSuffix("(${1:float a})")), + AllOf(named("foo3"), signature("(int a) const"), + snippetSuffix("(${1:int a})")), + AllOf(named("foo4"), signature("(int a)"), + snippetSuffix("(${1:int a})")))); + } + { + auto Result = codeComplete(testPath(TU.Filename), Code.point("c5"), + Preamble.get(), Inputs, Opts); + EXPECT_THAT( + Result.Completions, + UnorderedElementsAre(AllOf(named("foo1"), signature("(int a)"), + snippetSuffix("(${1:int a})")), + AllOf(named("foo2"), signature("(int a) const"), + snippetSuffix("(${1:int a})")), + AllOf(named("foo2"), signature("(float a) const"), + snippetSuffix("(${1:float a})")), + AllOf(named("foo3"), signature("(int a) const"), + snippetSuffix("(${1:int a})")), + AllOf(named("foo4"), signature("(int a)"), + snippetSuffix("(${1:int a})")))); + } + { + auto Result = codeComplete(testPath(TU.Filename), Code.point("c6"), + Preamble.get(), Inputs, Opts); + EXPECT_THAT( + Result.Completions, + UnorderedElementsAre(AllOf(named("foo2"), signature("(int a) const"), + snippetSuffix("(${1:int a})")), + AllOf(named("foo2"), signature("(float a) const"), + snippetSuffix("(${1:float a})")), + AllOf(named("foo3"), signature("(int a) const"), + snippetSuffix("(${1:int a})")))); + } +} + } // namespace } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp b/clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp index d71b8d5..d94e706 100644 --- a/clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp +++ b/clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp @@ -230,17 +230,19 @@ TEST(ParseYAML, CodePatterns) { EXPECT_THAT(Results[0].Completion.CodePatterns, llvm::ValueIs(val("None"))); } -TEST(ParseYAML, ShowAKA) { +TEST(ParseYAML, Hover) { CapturedDiags Diags; Annotations YAML(R"yaml( Hover: ShowAKA: True + MacroContentsLimit: 4096 )yaml"); auto Results = Fragment::parseYAML(YAML.code(), "config.yaml", Diags.callback()); ASSERT_THAT(Diags.Diagnostics, IsEmpty()); ASSERT_EQ(Results.size(), 1u); EXPECT_THAT(Results[0].Hover.ShowAKA, llvm::ValueIs(val(true))); + EXPECT_THAT(Results[0].Hover.MacroContentsLimit, llvm::ValueIs(val(4096U))); } TEST(ParseYAML, InlayHints) { diff --git a/clang-tools-extra/clangd/unittests/DumpASTTests.cpp b/clang-tools-extra/clangd/unittests/DumpASTTests.cpp index cb2c17a..5c857d0 100644 --- a/clang-tools-extra/clangd/unittests/DumpASTTests.cpp +++ b/clang-tools-extra/clangd/unittests/DumpASTTests.cpp @@ -72,15 +72,14 @@ declaration: Namespace - root expression: BinaryOperator - + expression: ImplicitCast - LValueToRValue expression: DeclRef - x - specifier: TypeSpec + specifier: Type type: Record - S expression: ImplicitCast - LValueToRValue expression: Member - x expression: CXXBindTemporary expression: CXXTemporaryObject - S - type: Elaborated + type: Record - S specifier: Namespace - root:: - type: Record - S )"}, {R"cpp( namespace root { @@ -104,14 +103,13 @@ declaration: Namespace - root expression: BinaryOperator - + expression: ImplicitCast - LValueToRValue expression: DeclRef - x - specifier: TypeSpec + specifier: Type type: Record - S expression: ImplicitCast - LValueToRValue expression: Member - x expression: CXXTemporaryObject - S - type: Elaborated + type: Record - S specifier: Namespace - root:: - type: Record - S )"}, {R"cpp( namespace root { @@ -138,7 +136,7 @@ declaration: Namespace - root type: Builtin - unsigned int statement: Return expression: DependentScopeDeclRef - value - specifier: TypeSpec + specifier: Type type: TemplateTypeParm - T )"}, {R"cpp( @@ -154,8 +152,7 @@ declaration: Var - root expression: DeclRef - operator+ expression: MaterializeTemporary - lvalue expression: CXXTemporaryObject - Foo - type: Elaborated - type: Record - Foo + type: Record - Foo expression: IntegerLiteral - 42 )"}, {R"cpp( diff --git a/clang-tools-extra/clangd/unittests/FindTargetTests.cpp b/clang-tools-extra/clangd/unittests/FindTargetTests.cpp index 4d77f9d..f369e1b 100644 --- a/clang-tools-extra/clangd/unittests/FindTargetTests.cpp +++ b/clang-tools-extra/clangd/unittests/FindTargetTests.cpp @@ -731,6 +731,12 @@ TEST_F(TargetDeclTest, BuiltinTemplates) { using type_pack_element = [[__type_pack_element]]<N, Pack...>; )cpp"; EXPECT_DECLS("TemplateSpecializationTypeLoc", ); + + Code = R"cpp( + template <template <class...> class Templ, class... Types> + using dedup_types = Templ<[[__builtin_dedup_pack]]<Types...>...>; + )cpp"; + EXPECT_DECLS("TemplateSpecializationTypeLoc", ); } TEST_F(TargetDeclTest, MemberOfTemplate) { @@ -992,7 +998,7 @@ TEST_F(TargetDeclTest, DependentTypes) { )cpp"; EXPECT_DECLS("DependentNameTypeLoc", "struct B"); - // Heuristic resolution of dependent type name which doesn't get a TypeLoc + // Heuristic resolution of dependent type name within a NestedNameSpecifierLoc Code = R"cpp( template <typename> struct A { struct B { struct C {}; }; }; @@ -1000,7 +1006,7 @@ TEST_F(TargetDeclTest, DependentTypes) { template <typename T> void foo(typename A<T>::[[B]]::C); )cpp"; - EXPECT_DECLS("NestedNameSpecifierLoc", "struct B"); + EXPECT_DECLS("DependentNameTypeLoc", "struct B"); // Heuristic resolution of dependent type name whose qualifier is also // dependent diff --git a/clang-tools-extra/clangd/unittests/HoverTests.cpp b/clang-tools-extra/clangd/unittests/HoverTests.cpp index 12d260d..743c0dc 100644 --- a/clang-tools-extra/clangd/unittests/HoverTests.cpp +++ b/clang-tools-extra/clangd/unittests/HoverTests.cpp @@ -2894,7 +2894,7 @@ TEST(Hover, All) { )cpp", [](HoverInfo &HI) { HI.Name = "this"; - HI.Definition = "const Foo<T> *"; + HI.Definition = "const ns::Foo<T> *"; }}, { R"cpp(// this expr for specialization class @@ -2910,7 +2910,7 @@ TEST(Hover, All) { )cpp", [](HoverInfo &HI) { HI.Name = "this"; - HI.Definition = "Foo<int> *"; + HI.Definition = "ns::Foo<int> *"; }}, { R"cpp(// this expr for partial specialization struct @@ -2926,7 +2926,7 @@ TEST(Hover, All) { )cpp", [](HoverInfo &HI) { HI.Name = "this"; - HI.Definition = "const Foo<int, F> *"; + HI.Definition = "const ns::Foo<int, F> *"; }}, { R"cpp( @@ -3046,8 +3046,8 @@ TEST(Hover, All) { HI.Kind = index::SymbolKind::Function; HI.NamespaceScope = ""; HI.Definition = "MyRect foobar()"; - HI.Type = {"MyRect ()", "MyRect ()"}; - HI.ReturnType = {"MyRect", "MyRect"}; + HI.Type = {"MyRect ()", "struct MyRect ()"}; + HI.ReturnType = {"MyRect", "struct MyRect"}; HI.Parameters.emplace(); }}, {R"cpp( @@ -3409,7 +3409,8 @@ TEST(Hover, DocsFromMostSpecial) { TEST(Hover, Present) { struct { const std::function<void(HoverInfo &)> Builder; - llvm::StringRef ExpectedRender; + llvm::StringRef ExpectedMarkdownRender; + llvm::StringRef ExpectedDoxygenRender; } Cases[] = { { [](HoverInfo &HI) { @@ -3417,6 +3418,7 @@ TEST(Hover, Present) { HI.Name = "X"; }, R"(X)", + R"(### `X`)", }, { [](HoverInfo &HI) { @@ -3424,6 +3426,7 @@ TEST(Hover, Present) { HI.Name = "foo"; }, R"(namespace-alias foo)", + R"(### namespace-alias `foo`)", }, { [](HoverInfo &HI) { @@ -3446,6 +3449,24 @@ Size: 10 bytes documentation template <typename T, typename C = bool> class Foo {})", + R"(### class + +--- +```cpp +template <typename T, typename C = bool> class Foo {} +``` + +--- +**Template Parameters:** + +- `typename T` +- `typename C = bool` + +--- +documentation + +--- +Size: 10 bytes)", }, { [](HoverInfo &HI) { @@ -3476,6 +3497,26 @@ template <typename T, typename C = bool> class Foo {})", "\n" "// In namespace ns\n" "ret_type foo(params) {}", + R"(### function + +--- +```cpp +// In namespace ns +ret_type foo(params) {} +``` + +--- +**Parameters:** + +- +- `type (aka can_type)` +- `type foo (aka can_type)` +- `type foo = default (aka can_type)` + +--- +**Returns:** + +`ret_type (aka can_ret_type)`)", }, { [](HoverInfo &HI) { @@ -3502,6 +3543,22 @@ Size: 4 bytes (+4 bytes padding), alignment 4 bytes // In test::Bar def)", + R"(### field + +--- +```cpp +// In test::Bar +def +``` + +--- +Type: `type (aka can_type)` + +Value = `value` + +Offset: 12 bytes + +Size: 4 bytes (+4 bytes padding), alignment 4 bytes)", }, { [](HoverInfo &HI) { @@ -3528,6 +3585,22 @@ Size: 25 bits (+4 bits padding), alignment 8 bytes // In test::Bar def)", + R"(### field + +--- +```cpp +// In test::Bar +def +``` + +--- +Type: `type (aka can_type)` + +Value = `value` + +Offset: 4 bytes and 3 bits + +Size: 25 bits (+4 bits padding), alignment 8 bytes)", }, { [](HoverInfo &HI) { @@ -3541,6 +3614,13 @@ def)", // In test::Bar public: def)", + R"(### field + +--- +```cpp +// In test::Bar +public: def +```)", }, { [](HoverInfo &HI) { @@ -3560,6 +3640,18 @@ public: def)", // In cls<int> protected: size_t method())", + R"(### instance-method + +--- +```cpp +// In cls<int> +protected: size_t method() +``` + +--- +**Returns:** + +`size_t (aka unsigned long)`)", }, { [](HoverInfo &HI) { @@ -3587,6 +3679,19 @@ Parameters: // In cls public: cls(int a, int b = 5))", + R"(### constructor + +--- +```cpp +// In cls +public: cls(int a, int b = 5) +``` + +--- +**Parameters:** + +- `int a` +- `int b = 5`)", }, { [](HoverInfo &HI) { @@ -3600,6 +3705,13 @@ public: cls(int a, int b = 5))", // In namespace ns1 private: union foo {})", + R"(### union + +--- +```cpp +// In namespace ns1 +private: union foo {} +```)", }, { [](HoverInfo &HI) { @@ -3625,6 +3737,20 @@ Passed as arg_a // In test::Bar int foo = 3)", + R"(### variable + +--- +```cpp +// In test::Bar +int foo = 3 +``` + +--- +Type: `int` + +Value = `3` + +Passed as arg_a)", }, { [](HoverInfo &HI) { @@ -3637,6 +3763,10 @@ int foo = 3)", R"(variable foo Passed by value)", + R"(### variable `foo` + +--- +Passed by value)", }, { [](HoverInfo &HI) { @@ -3662,6 +3792,20 @@ Passed by reference as arg_a // In test::Bar int foo = 3)", + R"(### variable + +--- +```cpp +// In test::Bar +int foo = 3 +``` + +--- +Type: `int` + +Value = `3` + +Passed by reference as arg_a)", }, { [](HoverInfo &HI) { @@ -3687,6 +3831,20 @@ Passed as arg_a (converted to alias_int) // In test::Bar int foo = 3)", + R"(### variable + +--- +```cpp +// In test::Bar +int foo = 3 +``` + +--- +Type: `int` + +Value = `3` + +Passed as arg_a (converted to alias_int))", }, { [](HoverInfo &HI) { @@ -3702,6 +3860,15 @@ int foo = 3)", // Expands to (1 + 1))", + R"(### macro + +--- +```cpp +#define PLUS_ONE(X) (X+1) + +// Expands to +(1 + 1) +```)", }, { [](HoverInfo &HI) { @@ -3727,38 +3894,283 @@ Passed by const reference as arg_a (converted to int) // In test::Bar int foo = 3)", + R"(### variable + +--- +```cpp +// In test::Bar +int foo = 3 +``` + +--- +Type: `int` + +Value = `3` + +Passed by const reference as arg_a (converted to int))", }, { [](HoverInfo &HI) { HI.Name = "stdio.h"; HI.Definition = "/usr/include/stdio.h"; + HI.Kind = index::SymbolKind::IncludeDirective; }, R"(stdio.h /usr/include/stdio.h)", + R"(### `stdio.h` + +`/usr/include/stdio.h`)", + }, + { + [](HoverInfo &HI) { + HI.Name = "foo.h"; + HI.UsedSymbolNames = {"Foo", "Bar", "Bar"}; + HI.Kind = index::SymbolKind::IncludeDirective; + }, + R"(foo.h + +provides Foo, Bar, Bar)", + R"(### `foo.h` + +--- +provides `Foo`, `Bar`, `Bar`)", }, {[](HoverInfo &HI) { HI.Name = "foo.h"; - HI.UsedSymbolNames = {"Foo", "Bar", "Bar"}; + HI.UsedSymbolNames = {"Foo", "Bar", "Baz", "Foobar", "Qux", "Quux"}; + HI.Kind = index::SymbolKind::IncludeDirective; }, R"(foo.h -provides Foo, Bar, Bar)"}, +provides Foo, Bar, Baz, Foobar, Qux and 1 more)", + R"(### `foo.h` + +--- +provides `Foo`, `Bar`, `Baz`, `Foobar`, `Qux` and 1 more)"}}; + + for (const auto &C : Cases) { + HoverInfo HI; + C.Builder(HI); + Config Cfg; + Cfg.Hover.ShowAKA = true; + Cfg.Documentation.CommentFormat = Config::CommentFormatPolicy::Markdown; + WithContextValue WithCfg(Config::Key, std::move(Cfg)); + EXPECT_EQ(HI.present(MarkupKind::PlainText), C.ExpectedMarkdownRender); + } + for (const auto &C : Cases) { + HoverInfo HI; + C.Builder(HI); + Config Cfg; + Cfg.Hover.ShowAKA = true; + Cfg.Documentation.CommentFormat = Config::CommentFormatPolicy::Doxygen; + WithContextValue WithCfg(Config::Key, std::move(Cfg)); + EXPECT_EQ(HI.present(MarkupKind::Markdown), C.ExpectedDoxygenRender); + } +} + +TEST(Hover, PresentDocumentation) { + struct { + const std::function<void(HoverInfo &)> Builder; + llvm::StringRef ExpectedMarkdownRender; + llvm::StringRef ExpectedDoxygenRender; + } Cases[] = { {[](HoverInfo &HI) { - HI.Name = "foo.h"; - HI.UsedSymbolNames = {"Foo", "Bar", "Baz", "Foobar", "Qux", "Quux"}; + HI.Kind = index::SymbolKind::Function; + HI.Documentation = "@brief brief doc\n\n" + "longer doc"; + HI.Definition = "void foo()"; + HI.Name = "foo"; }, - R"(foo.h + R"(### function `foo` + +--- +@brief brief doc + +longer doc + +--- +```cpp +void foo() +```)", + R"(### function -provides Foo, Bar, Baz, Foobar, Qux and 1 more)"}}; +--- +```cpp +void foo() +``` + +--- +brief doc + +--- +longer doc)"}, + {[](HoverInfo &HI) { + HI.Kind = index::SymbolKind::Function; + HI.Documentation = "@brief brief doc\n\n" + "longer doc"; + HI.Definition = "int foo()"; + HI.ReturnType = "int"; + HI.Name = "foo"; + }, + R"(### function `foo` + +--- +→ `int` + +@brief brief doc + +longer doc + +--- +```cpp +int foo() +```)", + R"(### function + +--- +```cpp +int foo() +``` + +--- +brief doc + +--- +**Returns:** + +`int` + +--- +longer doc)"}, + {[](HoverInfo &HI) { + HI.Kind = index::SymbolKind::Function; + HI.Documentation = "@brief brief doc\n\n" + "longer doc\n@param a this is a param\n@return it " + "returns something"; + HI.Definition = "int foo(int a)"; + HI.ReturnType = "int"; + HI.Name = "foo"; + HI.Parameters.emplace(); + HI.Parameters->emplace_back(); + HI.Parameters->back().Type = "int"; + HI.Parameters->back().Name = "a"; + }, + R"(### function `foo` + +--- +→ `int` + +Parameters: + +- `int a` + +@brief brief doc + +longer doc +@param a this is a param +@return it returns something + +--- +```cpp +int foo(int a) +```)", + R"(### function + +--- +```cpp +int foo(int a) +``` + +--- +brief doc + +--- +**Parameters:** + +- `int a` - this is a param + +--- +**Returns:** + +`int` - it returns something + +--- +longer doc)"}, + {[](HoverInfo &HI) { + HI.Kind = index::SymbolKind::Function; + HI.Documentation = "@brief brief doc\n\n" + "longer doc\n@param a this is a param\n@param b " + "does not exist\n@return it returns something"; + HI.Definition = "int foo(int a)"; + HI.ReturnType = "int"; + HI.Name = "foo"; + HI.Parameters.emplace(); + HI.Parameters->emplace_back(); + HI.Parameters->back().Type = "int"; + HI.Parameters->back().Name = "a"; + }, + R"(### function `foo` + +--- +→ `int` + +Parameters: + +- `int a` + +@brief brief doc + +longer doc +@param a this is a param +@param b does not exist +@return it returns something + +--- +```cpp +int foo(int a) +```)", + R"(### function + +--- +```cpp +int foo(int a) +``` + +--- +brief doc + +--- +**Parameters:** + +- `int a` - this is a param + +--- +**Returns:** + +`int` - it returns something + +--- +longer doc)"}, + }; for (const auto &C : Cases) { HoverInfo HI; C.Builder(HI); Config Cfg; Cfg.Hover.ShowAKA = true; + Cfg.Documentation.CommentFormat = Config::CommentFormatPolicy::Markdown; WithContextValue WithCfg(Config::Key, std::move(Cfg)); - EXPECT_EQ(HI.present(MarkupKind::PlainText), C.ExpectedRender); + EXPECT_EQ(HI.present(MarkupKind::Markdown), C.ExpectedMarkdownRender); + } + for (const auto &C : Cases) { + HoverInfo HI; + C.Builder(HI); + Config Cfg; + Cfg.Hover.ShowAKA = true; + Cfg.Documentation.CommentFormat = Config::CommentFormatPolicy::Doxygen; + WithContextValue WithCfg(Config::Key, std::move(Cfg)); + EXPECT_EQ(HI.present(MarkupKind::Markdown), C.ExpectedDoxygenRender); } } @@ -3936,6 +4348,21 @@ TEST(Hover, PresentRulers) { "```"; EXPECT_EQ(HI.present(MarkupKind::Markdown), ExpectedMarkdown); + llvm::StringRef ExpectedDoxygenMarkdown = // + "### variable\n" + "\n" + "---\n" + "```cpp\n" + "def\n" + "```\n\n" + "---\n" + "Value = `val`"; + Config Cfg; + Cfg.Hover.ShowAKA = true; + Cfg.Documentation.CommentFormat = Config::CommentFormatPolicy::Doxygen; + WithContextValue WithCfg(Config::Key, std::move(Cfg)); + EXPECT_EQ(HI.present(MarkupKind::Markdown), ExpectedDoxygenMarkdown); + llvm::StringRef ExpectedPlaintext = R"pt(variable foo Value = val @@ -4339,6 +4766,188 @@ constexpr u64 pow_with_mod(u64 a, u64 b, u64 p) { EXPECT_TRUE(H->Value); EXPECT_TRUE(H->Type); } + +TEST(Hover, HoverMacroContentsLimit) { + const char *const Code = + R"cpp( + #define C(A) A##A // Concatenate + #define E(A) C(A) // Expand + #define Z0032 00000000000000000000000000000000 + #define Z0064 E(Z0032) + #define Z0128 E(Z0064) + #define Z0256 E(Z0128) + #define Z0512 E(Z0256) + #define Z1024 E(Z0512) + #define Z2048 E(Z1024) + #define Z4096 E(Z2048) // 4096 zeroes + int main() { return [[^Z4096]]; } + )cpp"; + + struct { + uint32_t MacroContentsLimit; + const std::string ExpectedDefinition; + } Cases[] = { + // With a limit of 2048, the macro expansion should get dropped. + {2048, "#define Z4096 E(Z2048)"}, + // With a limit of 8192, the macro expansion should be fully expanded. + {8192, std::string("#define Z4096 E(Z2048)\n\n") + + std::string("// Expands to\n") + std::string(4096, '0')}, + }; + for (const auto &Case : Cases) { + SCOPED_TRACE(Code); + + Annotations T(Code); + TestTU TU = TestTU::withCode(T.code()); + auto AST = TU.build(); + Config Cfg; + Cfg.Hover.MacroContentsLimit = Case.MacroContentsLimit; + WithContextValue WithCfg(Config::Key, std::move(Cfg)); + auto H = getHover(AST, T.point(), format::getLLVMStyle(), nullptr); + ASSERT_TRUE(H); + + EXPECT_EQ(H->Definition, Case.ExpectedDefinition); + } +} + +TEST(Hover, FunctionParameters) { + struct { + const char *const Code; + const std::function<void(HoverInfo &)> ExpectedBuilder; + std::string ExpectedRender; + } Cases[] = { + {R"cpp(/// Function doc + void foo(int [[^a]]); + )cpp", + [](HoverInfo &HI) { + HI.Name = "a"; + HI.Kind = index::SymbolKind::Parameter; + HI.NamespaceScope = ""; + HI.LocalScope = "foo::"; + HI.Type = "int"; + HI.Definition = "int a"; + HI.Documentation = ""; + }, + "### param\n\n---\n```cpp\n// In foo\nint a\n```\n\n---\nType: `int`"}, + {R"cpp(/// Function doc + /// @param a this is doc for a + void foo(int [[^a]]); + )cpp", + [](HoverInfo &HI) { + HI.Name = "a"; + HI.Kind = index::SymbolKind::Parameter; + HI.NamespaceScope = ""; + HI.LocalScope = "foo::"; + HI.Type = "int"; + HI.Definition = "int a"; + HI.Documentation = "this is doc for a"; + }, + "### param\n\n---\n```cpp\n// In foo\nint a\n```\n\n---\nthis is doc " + "for a\n\n---\nType: `int`"}, + {R"cpp(/// Function doc + /// @param b this is doc for b + void foo(int [[^a]], int b); + )cpp", + [](HoverInfo &HI) { + HI.Name = "a"; + HI.Kind = index::SymbolKind::Parameter; + HI.NamespaceScope = ""; + HI.LocalScope = "foo::"; + HI.Type = "int"; + HI.Definition = "int a"; + HI.Documentation = ""; + }, + "### param\n\n---\n```cpp\n// In foo\nint a\n```\n\n---\nType: `int`"}, + {R"cpp(/// Function doc + /// @param b this is doc for \p b + void foo(int a, int [[^b]]); + )cpp", + [](HoverInfo &HI) { + HI.Name = "b"; + HI.Kind = index::SymbolKind::Parameter; + HI.NamespaceScope = ""; + HI.LocalScope = "foo::"; + HI.Type = "int"; + HI.Definition = "int b"; + HI.Documentation = "this is doc for \\p b"; + }, + "### param\n\n---\n```cpp\n// In foo\nint b\n```\n\n---\nthis is doc " + "for `b`\n\n---\nType: `int`"}, + {R"cpp(/// Function doc + /// @param b this is doc for \p b + template <typename T> + void foo(T a, T [[^b]]); + )cpp", + [](HoverInfo &HI) { + HI.Name = "b"; + HI.Kind = index::SymbolKind::Parameter; + HI.NamespaceScope = ""; + HI.LocalScope = "foo::"; + HI.Type = "T"; + HI.Definition = "T b"; + HI.Documentation = "this is doc for \\p b"; + }, + "### param\n\n---\n```cpp\n// In foo\nT b\n```\n\n---\nthis is doc for " + "`b`\n\n---\nType: `T`"}, + {R"cpp(/// Function doc + /// @param b this is <b>doc</b> <html-tag attribute/> <another-html-tag attribute="value">for</another-html-tag> \p b + void foo(int a, int [[^b]]); + )cpp", + [](HoverInfo &HI) { + HI.Name = "b"; + HI.Kind = index::SymbolKind::Parameter; + HI.NamespaceScope = ""; + HI.LocalScope = "foo::"; + HI.Type = "int"; + HI.Definition = "int b"; + HI.Documentation = + "this is <b>doc</b> <html-tag attribute/> <another-html-tag " + "attribute=\"value\">for</another-html-tag> \\p b"; + }, + "### param\n\n---\n```cpp\n// In foo\nint b\n```\n\n---\nthis is " + "\\<b>doc\\</b> \\<html-tag attribute/> \\<another-html-tag " + "attribute=\"value\">for\\</another-html-tag> `b`\n\n---\nType: `int`"}, + }; + + // Create a tiny index, so tests above can verify documentation is fetched. + Symbol IndexSym = func("indexSymbol"); + IndexSym.Documentation = "comment from index"; + SymbolSlab::Builder Symbols; + Symbols.insert(IndexSym); + auto Index = + MemIndex::build(std::move(Symbols).build(), RefSlab(), RelationSlab()); + + for (const auto &Case : Cases) { + SCOPED_TRACE(Case.Code); + + Annotations T(Case.Code); + TestTU TU = TestTU::withCode(T.code()); + auto AST = TU.build(); + Config Cfg; + Cfg.Hover.ShowAKA = true; + Cfg.Documentation.CommentFormat = Config::CommentFormatPolicy::Doxygen; + WithContextValue WithCfg(Config::Key, std::move(Cfg)); + auto H = getHover(AST, T.point(), format::getLLVMStyle(), Index.get()); + ASSERT_TRUE(H); + HoverInfo Expected; + Expected.SymRange = T.range(); + Case.ExpectedBuilder(Expected); + + EXPECT_EQ(H->present(MarkupKind::Markdown), Case.ExpectedRender); + EXPECT_EQ(H->NamespaceScope, Expected.NamespaceScope); + EXPECT_EQ(H->LocalScope, Expected.LocalScope); + EXPECT_EQ(H->Name, Expected.Name); + EXPECT_EQ(H->Kind, Expected.Kind); + EXPECT_EQ(H->Documentation, Expected.Documentation); + EXPECT_EQ(H->Definition, Expected.Definition); + EXPECT_EQ(H->Type, Expected.Type); + EXPECT_EQ(H->ReturnType, Expected.ReturnType); + EXPECT_EQ(H->Parameters, Expected.Parameters); + EXPECT_EQ(H->TemplateParameters, Expected.TemplateParameters); + EXPECT_EQ(H->SymRange, Expected.SymRange); + EXPECT_EQ(H->Value, Expected.Value); + } +} + } // namespace } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp index e0cd955..99e728c 100644 --- a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp +++ b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp @@ -1295,14 +1295,7 @@ TEST(TypeHints, NoQualifiers) { } } )cpp", - ExpectedHint{": S1", "x"}, - // FIXME: We want to suppress scope specifiers - // here because we are into the whole - // brevity thing, but the ElaboratedType - // printer does not honor the SuppressScope - // flag by design, so we need to extend the - // PrintingPolicy to support this use case. - ExpectedHint{": S2::Inner<int>", "y"}); + ExpectedHint{": S1", "x"}, ExpectedHint{": Inner<int>", "y"}); } TEST(TypeHints, Lambda) { diff --git a/clang-tools-extra/clangd/unittests/QualityTests.cpp b/clang-tools-extra/clangd/unittests/QualityTests.cpp index 619ea32..4954659 100644 --- a/clang-tools-extra/clangd/unittests/QualityTests.cpp +++ b/clang-tools-extra/clangd/unittests/QualityTests.cpp @@ -121,7 +121,9 @@ TEST(QualityTests, SymbolRelevanceSignalExtraction) { SymbolRelevanceSignals Relevance; Relevance.merge(CodeCompletionResult(&findDecl(AST, "deprecated"), - /*Priority=*/42, nullptr, false, + /*Priority=*/42, + /*Qualifier=*/std::nullopt, + /*QualifierIsInformative=*/false, /*Accessible=*/false)); EXPECT_EQ(Relevance.NameMatch, SymbolRelevanceSignals().NameMatch); EXPECT_TRUE(Relevance.Forbidden); @@ -487,13 +489,15 @@ TEST(QualityTests, ItemWithFixItsRankedDown) { auto AST = Header.build(); SymbolRelevanceSignals RelevanceWithFixIt; - RelevanceWithFixIt.merge(CodeCompletionResult(&findDecl(AST, "x"), 0, nullptr, - false, true, {FixItHint{}})); + RelevanceWithFixIt.merge(CodeCompletionResult( + &findDecl(AST, "x"), /*Priority=*/0, /*Qualifier=*/std::nullopt, + /*QualifierIsInformative=*/false, /*Accessible=*/true, {FixItHint{}})); EXPECT_TRUE(RelevanceWithFixIt.NeedsFixIts); SymbolRelevanceSignals RelevanceWithoutFixIt; - RelevanceWithoutFixIt.merge( - CodeCompletionResult(&findDecl(AST, "x"), 0, nullptr, false, true, {})); + RelevanceWithoutFixIt.merge(CodeCompletionResult( + &findDecl(AST, "x"), /*Priority=*/0, /*Qualifier=*/std::nullopt, + /*QualifierIsInformative=*/false, /*Accessible=*/true, {})); EXPECT_FALSE(RelevanceWithoutFixIt.NeedsFixIts); EXPECT_LT(RelevanceWithFixIt.evaluateHeuristics(), diff --git a/clang-tools-extra/clangd/unittests/RenameTests.cpp b/clang-tools-extra/clangd/unittests/RenameTests.cpp index 2cb0722..5d2a77b 100644 --- a/clang-tools-extra/clangd/unittests/RenameTests.cpp +++ b/clang-tools-extra/clangd/unittests/RenameTests.cpp @@ -17,10 +17,11 @@ #include "clang/Tooling/Core/Replacement.h" #include "llvm/ADT/STLExtras.h" #include "llvm/Support/MemoryBuffer.h" -#include <algorithm> #include "gmock/gmock.h" #include "gtest/gtest.h" +#include <algorithm> + namespace clang { namespace clangd { namespace { @@ -861,6 +862,25 @@ TEST(RenameTest, WithinFileRename) { void func([[Fo^o]] *f) {} )cpp", + + // rename with explicit object parameter + R"cpp( + struct Foo { + int [[memb^er]] {}; + auto&& getter1(this auto&& self) { + auto local = [&] { + return self.[[memb^er]]; + }(); + return local + self.[[memb^er]]; + } + auto&& getter2(this Foo&& self) { + return self.[[memb^er]]; + } + int normal() { + return this->[[mem^ber]] + [[memb^er]]; + } + }; + )cpp", }; llvm::StringRef NewName = "NewName"; for (llvm::StringRef T : Tests) { @@ -868,6 +888,7 @@ TEST(RenameTest, WithinFileRename) { Annotations Code(T); auto TU = TestTU::withCode(Code.code()); TU.ExtraArgs.push_back("-xobjective-c++"); + TU.ExtraArgs.push_back("-std=c++23"); auto AST = TU.build(); auto Index = TU.index(); for (const auto &RenamePos : Code.points()) { diff --git a/clang-tools-extra/clangd/unittests/SelectionTests.cpp b/clang-tools-extra/clangd/unittests/SelectionTests.cpp index aaaf758..3df19d8 100644 --- a/clang-tools-extra/clangd/unittests/SelectionTests.cpp +++ b/clang-tools-extra/clangd/unittests/SelectionTests.cpp @@ -104,9 +104,9 @@ TEST(SelectionTest, CommonAncestor) { { R"cpp( template <typename T> - int x = [[T::^U::]]ccc(); + int x = T::[[^U]]::ccc(); )cpp", - "NestedNameSpecifierLoc", + "DependentNameTypeLoc", }, { R"cpp( diff --git a/clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp b/clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp index 7ede19c..4efae25 100644 --- a/clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp +++ b/clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp @@ -371,6 +371,45 @@ TEST(FoldingRanges, PseudoParserWithoutLineFoldings) { //[[ foo /* bar */]] )cpp", + R"cpp( + //Ignore non-conditional directives + #define A 1 + + void func() {[[ + int Variable = 100; + + #ifdef FOO[[ + Variable = 1; + #if 1[[ + Variable = 4; + ]]#endif + ]]#else[[ + Variable = 2; + //handle nested directives + #if 1[[ + Variable = 3; + ]]#endif + ]]#endif + + + ]]} + )cpp", + R"cpp( + int Variable = 0; + #if defined(WALDO) + Variable = 1; + # + )cpp", + R"cpp( + int Variable = 0; + #if defined(WALDO)[[ + Variable = 1; + ]]#elif 1[[ + Variable = 2; + ]]#else + Variable = 3; + # + )cpp", }; for (const char *Test : Tests) { auto T = Annotations(Test); diff --git a/clang-tools-extra/clangd/unittests/SymbolDocumentationTests.cpp b/clang-tools-extra/clangd/unittests/SymbolDocumentationTests.cpp new file mode 100644 index 0000000..31a6a14 --- /dev/null +++ b/clang-tools-extra/clangd/unittests/SymbolDocumentationTests.cpp @@ -0,0 +1,215 @@ +//===-- SymbolDocumentationTests.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 "SymbolDocumentation.h" + +#include "support/Markup.h" +#include "clang/Basic/CommentOptions.h" +#include "llvm/ADT/StringRef.h" +#include "gtest/gtest.h" + +namespace clang { +namespace clangd { + +TEST(SymbolDocumentation, UnhandledDocs) { + + CommentOptions CommentOpts; + + struct Case { + llvm::StringRef Documentation; + llvm::StringRef ExpectedRenderEscapedMarkdown; + llvm::StringRef ExpectedRenderMarkdown; + llvm::StringRef ExpectedRenderPlainText; + } Cases[] = { + { + "foo bar", + "foo bar", + "foo bar", + "foo bar", + }, + { + "foo\nbar\n", + "foo\nbar", + "foo\nbar", + "foo bar", + }, + { + "foo\n\nbar\n", + "foo\n\nbar", + "foo\n\nbar", + "foo\n\nbar", + }, + { + "foo \\p bar baz", + "foo `bar` baz", + "foo `bar` baz", + "foo bar baz", + }, + { + "foo \\e bar baz", + "foo \\*bar\\* baz", + "foo *bar* baz", + "foo *bar* baz", + }, + { + "foo \\b bar baz", + "foo \\*\\*bar\\*\\* baz", + "foo **bar** baz", + "foo **bar** baz", + }, + { + "foo \\ref bar baz", + "foo \\*\\*\\\\ref\\*\\* \\*bar\\* baz", + "foo **\\ref** *bar* baz", + "foo **\\ref** *bar* baz", + }, + { + "foo @ref bar baz", + "foo \\*\\*@ref\\*\\* \\*bar\\* baz", + "foo **@ref** *bar* baz", + "foo **@ref** *bar* baz", + }, + { + "\\brief this is a \\n\nbrief description", + "", + "", + "", + }, + { + "\\throw exception foo", + "\\*\\*\\\\throw\\*\\* \\*exception\\* foo", + "**\\throw** *exception* foo", + "**\\throw** *exception* foo", + }, + { + R"(\brief this is a brief description + +\li item 1 +\li item 2 +\arg item 3)", + R"(- item 1 + +- item 2 + +- item 3)", + R"(- item 1 + +- item 2 + +- item 3)", + R"(- item 1 + +- item 2 + +- item 3)", + }, + { + "\\defgroup mygroup this is a group\nthis is not a group description", + "\\*\\*@defgroup\\*\\* `mygroup this is a group`\n\nthis is not a " + "group " + "description", + "**@defgroup** `mygroup this is a group`\n\nthis is not a group " + "description", + "**@defgroup** `mygroup this is a group`\n\nthis is not a group " + "description", + }, + { + R"(\verbatim +this is a +verbatim block containing +some verbatim text +\endverbatim)", + R"(\*\*@verbatim\*\* + +``` +this is a +verbatim block containing +some verbatim text +``` + +\*\*@endverbatim\*\*)", + R"(**@verbatim** + +``` +this is a +verbatim block containing +some verbatim text +``` + +**@endverbatim**)", + R"(**@verbatim** + +this is a +verbatim block containing +some verbatim text + +**@endverbatim**)", + }, + { + "@param foo this is a parameter\n@param bar this is another " + "parameter", + "", + "", + "", + }, + { + R"(@brief brief docs + +@param foo this is a parameter + +\brief another brief? + +\details these are details + +More description +documentation)", + R"(\*\*\\brief\*\* another brief? + +\*\*\\details\*\* these are details + +More description +documentation)", + R"(**\brief** another brief? + +**\details** these are details + +More description +documentation)", + R"(**\brief** another brief? + +**\details** these are details + +More description documentation)", + }, + { + R"(<b>this is a bold text</b> +normal text<i>this is an italic text</i> +<code>this is a code block</code>)", + R"(\<b>this is a bold text\</b> +normal text\<i>this is an italic text\</i> +\<code>this is a code block\</code>)", + R"(\<b>this is a bold text\</b> +normal text\<i>this is an italic text\</i> +\<code>this is a code block\</code>)", + "<b>this is a bold text</b> normal text<i>this is an italic text</i> " + "<code>this is a code block</code>", + }, + }; + for (const auto &C : Cases) { + markup::Document Doc; + SymbolDocCommentVisitor SymbolDoc(C.Documentation, CommentOpts); + + SymbolDoc.docToMarkup(Doc); + + EXPECT_EQ(Doc.asPlainText(), C.ExpectedRenderPlainText); + EXPECT_EQ(Doc.asMarkdown(), C.ExpectedRenderMarkdown); + EXPECT_EQ(Doc.asEscapedMarkdown(), C.ExpectedRenderEscapedMarkdown); + } +} + +} // namespace clangd +} // namespace clang diff --git a/clang-tools-extra/clangd/unittests/support/MarkupTests.cpp b/clang-tools-extra/clangd/unittests/support/MarkupTests.cpp index 482f230..5f91f31 100644 --- a/clang-tools-extra/clangd/unittests/support/MarkupTests.cpp +++ b/clang-tools-extra/clangd/unittests/support/MarkupTests.cpp @@ -463,6 +463,7 @@ TEST(Document, Separators) { ```cpp test ``` + bar)md"; EXPECT_EQ(D.asEscapedMarkdown(), ExpectedMarkdown); EXPECT_EQ(D.asMarkdown(), ExpectedMarkdown); @@ -559,6 +560,7 @@ foo bar baz ``` + ```cpp foo ```)md"; @@ -571,6 +573,12 @@ foo foo)pt"; EXPECT_EQ(D.asPlainText(), ExpectedPlainText); + + Document D2; + D2.addCodeBlock(""); + EXPECT_EQ(D2.asEscapedMarkdown(), "```cpp\n```"); + EXPECT_EQ(D2.asMarkdown(), "```cpp\n```"); + EXPECT_EQ(D2.asPlainText(), ""); } TEST(BulletList, Render) { |