Index: clang-tidy/boost/UseToStringCheck.cpp =================================================================== --- clang-tidy/boost/UseToStringCheck.cpp +++ clang-tidy/boost/UseToStringCheck.cpp @@ -15,10 +15,12 @@ namespace tidy { namespace boost { +namespace { AST_MATCHER(Type, isStrictlyInteger) { return Node.isIntegerType() && !Node.isAnyCharacterType() && !Node.isBooleanType(); } +} // namespace void UseToStringCheck::registerMatchers(MatchFinder *Finder) { if (!getLangOpts().CPlusPlus) Index: clang-tidy/bugprone/BugproneTidyModule.cpp =================================================================== --- clang-tidy/bugprone/BugproneTidyModule.cpp +++ clang-tidy/bugprone/BugproneTidyModule.cpp @@ -25,6 +25,7 @@ #include "MultipleStatementMacroCheck.h" #include "StringConstructorCheck.h" #include "SuspiciousMemsetUsageCheck.h" +#include "ThrowKeywordMissingCheck.h" #include "UndefinedMemoryManipulationCheck.h" #include "UseAfterMoveCheck.h" #include "VirtualNearMissCheck.h" @@ -66,6 +67,8 @@ "bugprone-string-constructor"); CheckFactories.registerCheck( "bugprone-suspicious-memset-usage"); + CheckFactories.registerCheck( + "bugprone-throw-keyword-missing"); CheckFactories.registerCheck( "bugprone-undefined-memory-manipulation"); CheckFactories.registerCheck( Index: clang-tidy/bugprone/CMakeLists.txt =================================================================== --- clang-tidy/bugprone/CMakeLists.txt +++ clang-tidy/bugprone/CMakeLists.txt @@ -17,6 +17,7 @@ MultipleStatementMacroCheck.cpp StringConstructorCheck.cpp SuspiciousMemsetUsageCheck.cpp + ThrowKeywordMissingCheck.cpp UndefinedMemoryManipulationCheck.cpp UseAfterMoveCheck.cpp VirtualNearMissCheck.cpp Index: clang-tidy/bugprone/StringConstructorCheck.cpp =================================================================== --- clang-tidy/bugprone/StringConstructorCheck.cpp +++ clang-tidy/bugprone/StringConstructorCheck.cpp @@ -18,9 +18,11 @@ namespace tidy { namespace bugprone { +namespace { AST_MATCHER_P(IntegerLiteral, isBiggerThan, unsigned, N) { return Node.getValue().getZExtValue() > N; } +} // namespace StringConstructorCheck::StringConstructorCheck(StringRef Name, ClangTidyContext *Context) Index: clang-tidy/bugprone/ThrowKeywordMissingCheck.h =================================================================== --- /dev/null +++ clang-tidy/bugprone/ThrowKeywordMissingCheck.h @@ -0,0 +1,36 @@ +//===--- ThrowKeywordMissingCheck.h - clang-tidy-----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_THROWKEYWORDMISSINGCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_THROWKEYWORDMISSINGCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace bugprone { + +/// Emits a warning about temporary objects whose type is (or is derived from) a +/// class that has 'EXCEPTION', 'Exception' or 'exception' in its name. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone-throw-keyword-missing.html +class ThrowKeywordMissingCheck : public ClangTidyCheck { +public: + ThrowKeywordMissingCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace bugprone +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_THROWKEYWORDMISSINGCHECK_H Index: clang-tidy/bugprone/ThrowKeywordMissingCheck.cpp =================================================================== --- /dev/null +++ clang-tidy/bugprone/ThrowKeywordMissingCheck.cpp @@ -0,0 +1,52 @@ +//===--- ThrowKeywordMissingCheck.cpp - clang-tidy-------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ThrowKeywordMissingCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace bugprone { + +void ThrowKeywordMissingCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + auto CtorInitializerList = + cxxConstructorDecl(hasAnyConstructorInitializer(anything())); + + Finder->addMatcher( + expr(anyOf(cxxFunctionalCastExpr(), cxxBindTemporaryExpr(), + cxxTemporaryObjectExpr()), + hasType(cxxRecordDecl( + isSameOrDerivedFrom(matchesName("[Ee]xception|EXCEPTION")))), + unless(anyOf(hasAncestor(stmt( + anyOf(cxxThrowExpr(), callExpr(), returnStmt()))), + hasAncestor(varDecl()), + allOf(hasAncestor(CtorInitializerList), + unless(hasAncestor(cxxCatchStmt())))))) + .bind("temporary-exception-not-thrown"), + this); +} + +void ThrowKeywordMissingCheck::check(const MatchFinder::MatchResult &Result) { + const auto *TemporaryExpr = + Result.Nodes.getNodeAs("temporary-exception-not-thrown"); + + diag(TemporaryExpr->getLocStart(), "suspicious exception object created but " + "not thrown; did you mean 'throw %0'?") + << TemporaryExpr->getType().getBaseTypeIdentifier()->getName(); +} + +} // namespace bugprone +} // namespace tidy +} // namespace clang Index: clang-tidy/bugprone/VirtualNearMissCheck.cpp =================================================================== --- clang-tidy/bugprone/VirtualNearMissCheck.cpp +++ clang-tidy/bugprone/VirtualNearMissCheck.cpp @@ -19,11 +19,13 @@ namespace tidy { namespace bugprone { +namespace { AST_MATCHER(CXXMethodDecl, isStatic) { return Node.isStatic(); } AST_MATCHER(CXXMethodDecl, isOverloadedOperator) { return Node.isOverloadedOperator(); } +} // namespace /// Finds out if the given method overrides some method. static bool isOverrideMethod(const CXXMethodDecl *MD) { Index: clang-tidy/cppcoreguidelines/AvoidGotoCheck.cpp =================================================================== --- clang-tidy/cppcoreguidelines/AvoidGotoCheck.cpp +++ clang-tidy/cppcoreguidelines/AvoidGotoCheck.cpp @@ -17,9 +17,11 @@ namespace tidy { namespace cppcoreguidelines { +namespace { AST_MATCHER(GotoStmt, isForwardJumping) { return Node.getLocStart() < Node.getLabel()->getLocStart(); } +} // namespace void AvoidGotoCheck::registerMatchers(MatchFinder *Finder) { if (!getLangOpts().CPlusPlus) Index: clang-tidy/cppcoreguidelines/ProBoundsArrayToPointerDecayCheck.cpp =================================================================== --- clang-tidy/cppcoreguidelines/ProBoundsArrayToPointerDecayCheck.cpp +++ clang-tidy/cppcoreguidelines/ProBoundsArrayToPointerDecayCheck.cpp @@ -17,6 +17,7 @@ namespace tidy { namespace cppcoreguidelines { +namespace { AST_MATCHER_P(CXXForRangeStmt, hasRangeBeginEndStmt, ast_matchers::internal::Matcher, InnerMatcher) { for (const DeclStmt *Stmt : {Node.getBeginStmt(), Node.getEndStmt()}) @@ -46,6 +47,7 @@ return InnerMatcher.matches(*E, Finder, Builder); } +} // namespace void ProBoundsArrayToPointerDecayCheck::registerMatchers(MatchFinder *Finder) { if (!getLangOpts().CPlusPlus) Index: clang-tidy/fuchsia/MultipleInheritanceCheck.cpp =================================================================== --- clang-tidy/fuchsia/MultipleInheritanceCheck.cpp +++ clang-tidy/fuchsia/MultipleInheritanceCheck.cpp @@ -17,12 +17,14 @@ namespace clang { namespace tidy { namespace fuchsia { - + +namespace { AST_MATCHER(CXXRecordDecl, hasBases) { if (Node.hasDefinition()) return Node.getNumBases() > 0; return false; } +} // namespace // Adds a node (by name) to the interface map, if it was not present in the map // previously. Index: clang-tidy/fuchsia/OverloadedOperatorCheck.cpp =================================================================== --- clang-tidy/fuchsia/OverloadedOperatorCheck.cpp +++ clang-tidy/fuchsia/OverloadedOperatorCheck.cpp @@ -15,6 +15,7 @@ namespace tidy { namespace fuchsia { +namespace { AST_MATCHER(FunctionDecl, isFuchsiaOverloadedOperator) { if (const auto *CXXMethodNode = dyn_cast(&Node)) { if (CXXMethodNode->isCopyAssignmentOperator() || @@ -23,6 +24,7 @@ } return Node.isOverloadedOperator(); } +} // namespace void OverloadedOperatorCheck::registerMatchers(MatchFinder *Finder) { Finder->addMatcher(functionDecl(isFuchsiaOverloadedOperator()).bind("decl"), Index: clang-tidy/fuchsia/StaticallyConstructedObjectsCheck.cpp =================================================================== --- clang-tidy/fuchsia/StaticallyConstructedObjectsCheck.cpp +++ clang-tidy/fuchsia/StaticallyConstructedObjectsCheck.cpp @@ -14,7 +14,8 @@ namespace clang { namespace tidy { namespace fuchsia { - + +namespace { AST_MATCHER(Expr, isConstantInitializer) { return Node.isConstantInitializer(Finder->getASTContext(), false); } @@ -22,7 +23,8 @@ AST_MATCHER(VarDecl, isGlobalStatic) { return Node.getStorageDuration() == SD_Static && !Node.isLocalVarDecl(); } - +} // namespace + void StaticallyConstructedObjectsCheck::registerMatchers(MatchFinder *Finder) { // Constructing global, non-trivial objects with static storage is // disallowed, unless the object is statically initialized with a constexpr Index: clang-tidy/fuchsia/TrailingReturnCheck.cpp =================================================================== --- clang-tidy/fuchsia/TrailingReturnCheck.cpp +++ clang-tidy/fuchsia/TrailingReturnCheck.cpp @@ -15,18 +15,16 @@ using namespace clang::ast_matchers; namespace clang { -namespace ast_matchers { - -const internal::VariadicDynCastAllOfMatcher decltypeType; - -} // namespace ast_matchers - namespace tidy { namespace fuchsia { +namespace { +const internal::VariadicDynCastAllOfMatcher decltypeType; + AST_MATCHER(FunctionDecl, hasTrailingReturn) { return Node.getType()->castAs()->hasTrailingReturn(); } +} // namespace void TrailingReturnCheck::registerMatchers(MatchFinder *Finder) { Index: clang-tidy/fuchsia/VirtualInheritanceCheck.cpp =================================================================== --- clang-tidy/fuchsia/VirtualInheritanceCheck.cpp +++ clang-tidy/fuchsia/VirtualInheritanceCheck.cpp @@ -17,6 +17,7 @@ namespace tidy { namespace fuchsia { +namespace { AST_MATCHER(CXXRecordDecl, hasDirectVirtualBaseClass) { if (!Node.hasDefinition()) return false; if (!Node.getNumVBases()) return false; @@ -24,6 +25,7 @@ if (Base.isVirtual()) return true; return false; } +} // namespace void VirtualInheritanceCheck::registerMatchers(MatchFinder *Finder) { // Defining classes using direct virtual inheritance is disallowed. Index: clang-tidy/hicpp/NoAssemblerCheck.cpp =================================================================== --- clang-tidy/hicpp/NoAssemblerCheck.cpp +++ clang-tidy/hicpp/NoAssemblerCheck.cpp @@ -14,17 +14,16 @@ using namespace clang::ast_matchers; namespace clang { -namespace ast_matchers { -AST_MATCHER(VarDecl, isAsm) { return Node.hasAttr(); } -const internal::VariadicDynCastAllOfMatcher - fileScopeAsmDecl; -} -} - -namespace clang { namespace tidy { namespace hicpp { +namespace { +AST_MATCHER(VarDecl, isAsm) { return Node.hasAttr(); } +const ast_matchers::internal::VariadicDynCastAllOfMatcher + fileScopeAsmDecl; +} // namespace + void NoAssemblerCheck::registerMatchers(MatchFinder *Finder) { Finder->addMatcher(asmStmt().bind("asm-stmt"), this); Finder->addMatcher(fileScopeAsmDecl().bind("asm-file-scope"), this); Index: clang-tidy/misc/StringLiteralWithEmbeddedNulCheck.cpp =================================================================== --- clang-tidy/misc/StringLiteralWithEmbeddedNulCheck.cpp +++ clang-tidy/misc/StringLiteralWithEmbeddedNulCheck.cpp @@ -17,12 +17,14 @@ namespace tidy { namespace misc { +namespace { AST_MATCHER(StringLiteral, containsNul) { for (size_t i = 0; i < Node.getLength(); ++i) if (Node.getCodeUnit(i) == '\0') return true; return false; } +} // namespace void StringLiteralWithEmbeddedNulCheck::registerMatchers(MatchFinder *Finder) { // Match a string that contains embedded NUL character. Extra-checks are Index: clang-tidy/modernize/CMakeLists.txt =================================================================== --- clang-tidy/modernize/CMakeLists.txt +++ clang-tidy/modernize/CMakeLists.txt @@ -27,6 +27,7 @@ UseNullptrCheck.cpp UseOverrideCheck.cpp UseTransparentFunctorsCheck.cpp + UseUncaughtExceptionsCheck.cpp UseUsingCheck.cpp LINK_LIBS Index: clang-tidy/modernize/ModernizeTidyModule.cpp =================================================================== --- clang-tidy/modernize/ModernizeTidyModule.cpp +++ clang-tidy/modernize/ModernizeTidyModule.cpp @@ -33,6 +33,7 @@ #include "UseNullptrCheck.h" #include "UseOverrideCheck.h" #include "UseTransparentFunctorsCheck.h" +#include "UseUncaughtExceptionsCheck.h" #include "UseUsingCheck.h" using namespace clang::ast_matchers; @@ -78,6 +79,8 @@ CheckFactories.registerCheck("modernize-use-override"); CheckFactories.registerCheck( "modernize-use-transparent-functors"); + CheckFactories.registerCheck( + "modernize-use-uncaught-exceptions"); CheckFactories.registerCheck("modernize-use-using"); } Index: clang-tidy/modernize/PassByValueCheck.cpp =================================================================== --- clang-tidy/modernize/PassByValueCheck.cpp +++ clang-tidy/modernize/PassByValueCheck.cpp @@ -23,6 +23,7 @@ namespace tidy { namespace modernize { +namespace { /// \brief Matches move-constructible classes. /// /// Given @@ -44,6 +45,7 @@ } return false; } +} // namespace static TypeMatcher constRefType() { return lValueReferenceType(pointee(isConstQualified())); Index: clang-tidy/modernize/ReplaceAutoPtrCheck.cpp =================================================================== --- clang-tidy/modernize/ReplaceAutoPtrCheck.cpp +++ clang-tidy/modernize/ReplaceAutoPtrCheck.cpp @@ -21,6 +21,7 @@ namespace tidy { namespace modernize { +namespace { static const char AutoPtrTokenId[] = "AutoPrTokenId"; static const char AutoPtrOwnershipTransferId[] = "AutoPtrOwnershipTransferId"; @@ -69,6 +70,8 @@ return (Info && Info->isStr("std")); } +} // namespace + ReplaceAutoPtrCheck::ReplaceAutoPtrCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context), Index: clang-tidy/modernize/UseUncaughtExceptionsCheck.h =================================================================== --- /dev/null +++ clang-tidy/modernize/UseUncaughtExceptionsCheck.h @@ -0,0 +1,37 @@ +//===--- UseUncaughtExceptionsCheck.h - clang-tidy------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_UNCAUGHT_EXCEPTIONS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_UNCAUGHT_EXCEPTIONS_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace modernize { + +/// This check will warn on calls to std::uncaught_exception and replace them with calls to +/// std::uncaught_exceptions, since std::uncaught_exception was deprecated in C++17. In case of +/// macro ID there will be only a warning without fixits. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/modernize-use-uncaught-exceptions.html +class UseUncaughtExceptionsCheck : public ClangTidyCheck { +public: + UseUncaughtExceptionsCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_UNCAUGHT_EXCEPTIONS_H Index: clang-tidy/modernize/UseUncaughtExceptionsCheck.cpp =================================================================== --- /dev/null +++ clang-tidy/modernize/UseUncaughtExceptionsCheck.cpp @@ -0,0 +1,104 @@ +//===--- UseUncaughtExceptionsCheck.cpp - clang-tidy--------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UseUncaughtExceptionsCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace modernize { + +void UseUncaughtExceptionsCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus17) + return; + + std::string MatchText = "::std::uncaught_exception"; + + // Using declaration: warning and fix-it. + Finder->addMatcher( + usingDecl(hasAnyUsingShadowDecl(hasTargetDecl(hasName(MatchText)))) + .bind("using_decl"), + this); + + // DeclRefExpr: warning, no fix-it. + Finder->addMatcher(declRefExpr(allOf(to(functionDecl(hasName(MatchText))), + unless(callExpr()))) + .bind("decl_ref_expr"), + this); + + // CallExpr: warning, fix-it. + Finder->addMatcher( + callExpr(allOf(hasDeclaration(functionDecl(hasName(MatchText))), + unless(hasAncestor(initListExpr())))) + .bind("call_expr"), + this); + // CallExpr in initialisation list: warning, fix-it with avoiding narrowing + // conversions. + Finder->addMatcher( + callExpr(allOf(hasAncestor(initListExpr()), + hasDeclaration(functionDecl(hasName(MatchText))))) + .bind("init_call_expr"), + this); +} + +void UseUncaughtExceptionsCheck::check(const MatchFinder::MatchResult &Result) { + SourceLocation BeginLoc; + SourceLocation EndLoc; + const CallExpr *C = Result.Nodes.getNodeAs("init_call_expr"); + bool WarnOnly = false; + + if (C) { + BeginLoc = C->getLocStart(); + EndLoc = C->getLocEnd(); + } else if (const auto *E = Result.Nodes.getNodeAs("call_expr")) { + BeginLoc = E->getLocStart(); + EndLoc = E->getLocEnd(); + } else if (const auto *D = + Result.Nodes.getNodeAs("decl_ref_expr")) { + BeginLoc = D->getLocStart(); + EndLoc = D->getLocEnd(); + WarnOnly = true; + } else { + const auto *U = Result.Nodes.getNodeAs("using_decl"); + assert(U && "Null pointer, no node provided"); + BeginLoc = U->getNameInfo().getBeginLoc(); + EndLoc = U->getNameInfo().getEndLoc(); + } + + auto Diag = diag(BeginLoc, "'std::uncaught_exception' is deprecated, use " + "'std::uncaught_exceptions' instead"); + + if (!BeginLoc.isMacroID()) { + StringRef Text = + Lexer::getSourceText(CharSourceRange::getTokenRange(BeginLoc, EndLoc), + *Result.SourceManager, getLangOpts()); + + Text.consume_back("()"); + int TextLength = Text.size(); + + if (WarnOnly) { + return; + } + + if (!C) { + Diag << FixItHint::CreateInsertion(BeginLoc.getLocWithOffset(TextLength), + "s"); + } else { + Diag << FixItHint::CreateReplacement(C->getSourceRange(), + "std::uncaught_exceptions() > 0"); + } + } +} + +} // namespace modernize +} // namespace tidy +} // namespace clang Index: clang-tidy/readability/CMakeLists.txt =================================================================== --- clang-tidy/readability/CMakeLists.txt +++ clang-tidy/readability/CMakeLists.txt @@ -24,6 +24,7 @@ RedundantStringCStrCheck.cpp RedundantSmartptrGetCheck.cpp RedundantStringInitCheck.cpp + SIMDIntrinsicsCheck.cpp SimplifyBooleanExprCheck.cpp StaticAccessedThroughInstanceCheck.cpp StaticDefinitionInAnonymousNamespaceCheck.cpp Index: clang-tidy/readability/ReadabilityTidyModule.cpp =================================================================== --- clang-tidy/readability/ReadabilityTidyModule.cpp +++ clang-tidy/readability/ReadabilityTidyModule.cpp @@ -31,6 +31,7 @@ #include "RedundantSmartptrGetCheck.h" #include "RedundantStringCStrCheck.h" #include "RedundantStringInitCheck.h" +#include "SIMDIntrinsicsCheck.h" #include "SimplifyBooleanExprCheck.h" #include "StaticAccessedThroughInstanceCheck.h" #include "StaticDefinitionInAnonymousNamespaceCheck.h" @@ -92,6 +93,8 @@ "readability-redundant-string-cstr"); CheckFactories.registerCheck( "readability-redundant-string-init"); + CheckFactories.registerCheck( + "readability-simd-intrinsics"); CheckFactories.registerCheck( "readability-simplify-boolean-expr"); CheckFactories.registerCheck( Index: clang-tidy/readability/SIMDIntrinsicsCheck.h =================================================================== --- /dev/null +++ clang-tidy/readability/SIMDIntrinsicsCheck.h @@ -0,0 +1,40 @@ +//===--- SIMDIntrinsicsCheck.h - clang-tidy----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_SIMD_INTRINSICS_CHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_SIMD_INTRINSICS_CHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// Find SIMD intrinsics calls and suggest std::experimental::simd alternatives. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability-simd-intrinsics.html +class SIMDIntrinsicsCheck : public ClangTidyCheck { +public: + SIMDIntrinsicsCheck(StringRef Name, ClangTidyContext *Context); + + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + + private: + const bool Suggest; + StringRef Std; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_SIMD_INTRINSICS_CHECK_H Index: clang-tidy/readability/SIMDIntrinsicsCheck.cpp =================================================================== --- /dev/null +++ clang-tidy/readability/SIMDIntrinsicsCheck.cpp @@ -0,0 +1,152 @@ +//===--- SIMDIntrinsicsCheck.cpp - clang-tidy------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "SIMDIntrinsicsCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/ADT/Triple.h" +#include "llvm/Support/Regex.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { + +namespace { + +// If the callee has parameter of VectorType or pointer to VectorType, +// or the return type is VectorType, we consider it a vector function +// and a candidate for checking. +AST_MATCHER(FunctionDecl, isVectorFunction) { + bool IsVector = Node.getReturnType()->isVectorType(); + for (const ParmVarDecl *Parm : Node.parameters()) { + QualType Type = Parm->getType(); + if (Type->isPointerType()) + Type = Type->getPointeeType(); + if (Type->isVectorType()) + IsVector = true; + } + return IsVector; +} + +} // namespace + +static StringRef TrySuggestPPC(StringRef Name) { + if (!Name.consume_front("vec_")) + return {}; + + static const llvm::StringMap Mapping{ + // [simd.alg] + {"max", "$std::max"}, + {"min", "$std::min"}, + + // [simd.binary] + {"add", "operator+ on $simd objects"}, + {"sub", "operator- on $simd objects"}, + {"mul", "operator* on $simd objects"}, + }; + + auto It = Mapping.find(Name); + if (It != Mapping.end()) + return It->second; + return {}; +} + +static StringRef TrySuggestX86(StringRef Name) { + if (!(Name.consume_front("_mm_") || Name.consume_front("_mm256_") || + Name.consume_front("_mm512_"))) + return {}; + + // [simd.alg] + if (Name.startswith("max_")) + return "$simd::max"; + if (Name.startswith("min_")) + return "$simd::min"; + + // [simd.binary] + if (Name.startswith("add_")) + return "operator+ on $simd objects"; + if (Name.startswith("sub_")) + return "operator- on $simd objects"; + if (Name.startswith("mul_")) + return "operator* on $simd objects"; + + return {}; +} + +SIMDIntrinsicsCheck::SIMDIntrinsicsCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), Suggest(Options.get("Suggest", 0) != 0) {} + +void SIMDIntrinsicsCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "Suggest", 0); +} + +void SIMDIntrinsicsCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus11) + return; + // libcxx implementation backports it to C++11 std::experimental::simd. + Std = getLangOpts().CPlusPlus2a ? "std" : "std::experimental"; + + Finder->addMatcher(callExpr(callee(functionDecl(allOf( + matchesName("^::(_mm_|_mm256_|_mm512_|vec_)"), + isVectorFunction()))), + unless(isExpansionInSystemHeader())) + .bind("call"), + this); +} + +void SIMDIntrinsicsCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Call = Result.Nodes.getNodeAs("call"); + assert(Call != nullptr); + const FunctionDecl *Callee = Call->getDirectCallee(); + if (!Callee) + return; + + StringRef Old = Callee->getName(); + StringRef New; + llvm::Triple::ArchType Arch = + Result.Context->getTargetInfo().getTriple().getArch(); + + switch (Arch) { + default: + break; + case llvm::Triple::ppc: + case llvm::Triple::ppc64: + case llvm::Triple::ppc64le: + New = TrySuggestPPC(Old); + break; + case llvm::Triple::x86: + case llvm::Triple::x86_64: + New = TrySuggestX86(Old); + break; + } + + if (!New.empty()) { + std::string Message; + // If Suggest is true, give a P0214 alternative, otherwise point it out it + // is non-portable. + if (Suggest) { + Message = (Twine("'") + Old + "' can be replaced by " + New).str(); + Message = llvm::Regex("\\$std").sub(Std, Message); + Message = llvm::Regex("\\$simd").sub(Std.str() + "::simd", Message); + } else { + Message = (Twine("'") + Old + "' is a non-portable " + + llvm::Triple::getArchTypeName(Arch) + " intrinsic function") + .str(); + } + diag(Call->getExprLoc(), Message); + } +} + +} // namespace readability +} // namespace tidy +} // namespace clang Index: clangd/CMakeLists.txt =================================================================== --- clangd/CMakeLists.txt +++ clangd/CMakeLists.txt @@ -14,6 +14,7 @@ DraftStore.cpp FuzzyMatch.cpp GlobalCompilationDatabase.cpp + Headers.cpp JSONExpr.cpp JSONRPCDispatcher.cpp Logger.cpp @@ -25,6 +26,7 @@ TUScheduler.cpp URI.cpp XRefs.cpp + index/CanonicalIncludes.cpp index/FileIndex.cpp index/Index.cpp index/MemIndex.cpp Index: clangd/ClangdLSPServer.h =================================================================== --- clangd/ClangdLSPServer.h +++ clangd/ClangdLSPServer.h @@ -75,6 +75,8 @@ void onFileEvent(DidChangeWatchedFilesParams &Params) override; void onCommand(ExecuteCommandParams &Params) override; void onRename(RenameParams &Parames) override; + void onHover(TextDocumentPositionParams &Params) override; + void onChangeConfiguration(DidChangeConfigurationParams &Params) override; std::vector getFixIts(StringRef File, const clangd::Diagnostic &D); Index: clangd/ClangdLSPServer.cpp =================================================================== --- clangd/ClangdLSPServer.cpp +++ clangd/ClangdLSPServer.cpp @@ -87,6 +87,14 @@ } // namespace void ClangdLSPServer::onInitialize(InitializeParams &Params) { + if (Params.rootUri && *Params.rootUri) + Server.setRootPath(Params.rootUri->file()); + else if (Params.rootPath && !Params.rootPath->empty()) + Server.setRootPath(*Params.rootPath); + + CCOpts.EnableSnippets = + Params.capabilities.textDocument.completion.completionItem.snippetSupport; + reply(json::obj{ {{"capabilities", json::obj{ @@ -110,16 +118,15 @@ }}, {"definitionProvider", true}, {"documentHighlightProvider", true}, + {"hoverProvider", true}, {"renameProvider", true}, {"executeCommandProvider", json::obj{ - {"commands", {ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND}}, + {"commands", + {ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND, + ExecuteCommandParams::CLANGD_INSERT_HEADER_INCLUDE}}, }}, }}}}); - if (Params.rootUri && !Params.rootUri->file.empty()) - Server.setRootPath(Params.rootUri->file); - else if (Params.rootPath && !Params.rootPath->empty()) - Server.setRootPath(*Params.rootPath); } void ClangdLSPServer::onShutdown(ShutdownParams &Params) { @@ -132,9 +139,9 @@ void ClangdLSPServer::onDocumentDidOpen(DidOpenTextDocumentParams &Params) { if (Params.metadata && !Params.metadata->extraFlags.empty()) - CDB.setExtraFlagsForFile(Params.textDocument.uri.file, + CDB.setExtraFlagsForFile(Params.textDocument.uri.file(), std::move(Params.metadata->extraFlags)); - Server.addDocument(Params.textDocument.uri.file, Params.textDocument.text); + Server.addDocument(Params.textDocument.uri.file(), Params.textDocument.text); } void ClangdLSPServer::onDocumentDidChange(DidChangeTextDocumentParams &Params) { @@ -142,7 +149,7 @@ return replyError(ErrorCode::InvalidParams, "can only apply one change at a time"); // We only support full syncing right now. - Server.addDocument(Params.textDocument.uri.file, + Server.addDocument(Params.textDocument.uri.file(), Params.contentChanges[0].text); } @@ -151,6 +158,14 @@ } void ClangdLSPServer::onCommand(ExecuteCommandParams &Params) { + auto ApplyEdit = [](WorkspaceEdit WE) { + ApplyWorkspaceEditParams Edit; + Edit.edit = std::move(WE); + // We don't need the response so id == 1 is OK. + // Ideally, we would wait for the response and if there is no error, we + // would reply success/failure to the original RPC. + call("workspace/applyEdit", Edit); + }; if (Params.command == ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND && Params.workspaceEdit) { // The flow for "apply-fix" : @@ -162,13 +177,35 @@ // 6. The editor applies the changes (applyEdit), and sends us a reply (but // we ignore it) - ApplyWorkspaceEditParams ApplyEdit; - ApplyEdit.edit = *Params.workspaceEdit; reply("Fix applied."); - // We don't need the response so id == 1 is OK. - // Ideally, we would wait for the response and if there is no error, we - // would reply success/failure to the original RPC. - call("workspace/applyEdit", ApplyEdit); + ApplyEdit(*Params.workspaceEdit); + } else if (Params.command == + ExecuteCommandParams::CLANGD_INSERT_HEADER_INCLUDE) { + auto &FileURI = Params.includeInsertion->textDocument.uri; + auto Code = Server.getDocument(FileURI.file()); + if (!Code) + return replyError(ErrorCode::InvalidParams, + ("command " + + ExecuteCommandParams::CLANGD_INSERT_HEADER_INCLUDE + + " called on non-added file " + FileURI.file()) + .str()); + auto Replaces = Server.insertInclude(FileURI.file(), *Code, + Params.includeInsertion->header); + if (!Replaces) { + std::string ErrMsg = + ("Failed to generate include insertion edits for adding " + + Params.includeInsertion->header + " into " + FileURI.file()) + .str(); + log(ErrMsg + ":" + llvm::toString(Replaces.takeError())); + replyError(ErrorCode::InternalError, ErrMsg); + return; + } + auto Edits = replacementsToEdits(*Code, *Replaces); + WorkspaceEdit WE; + WE.changes = {{FileURI.uri(), Edits}}; + + reply("Inserted header " + Params.includeInsertion->header); + ApplyEdit(std::move(WE)); } else { // We should not get here because ExecuteCommandParams would not have // parsed in the first place and this handler should not be called. But if @@ -180,32 +217,34 @@ } void ClangdLSPServer::onRename(RenameParams &Params) { - auto File = Params.textDocument.uri.file; - auto Code = Server.getDocument(File); + Path File = Params.textDocument.uri.file(); + llvm::Optional Code = Server.getDocument(File); if (!Code) return replyError(ErrorCode::InvalidParams, "onRename called for non-added file"); - auto Replacements = Server.rename(File, Params.position, Params.newName); - if (!Replacements) { - replyError(ErrorCode::InternalError, - llvm::toString(Replacements.takeError())); - return; - } - - std::vector Edits = replacementsToEdits(*Code, *Replacements); - WorkspaceEdit WE; - WE.changes = {{Params.textDocument.uri.uri(), Edits}}; - reply(WE); + Server.rename( + File, Params.position, Params.newName, + [File, Code, + Params](llvm::Expected> Replacements) { + if (!Replacements) + return replyError(ErrorCode::InternalError, + llvm::toString(Replacements.takeError())); + + std::vector Edits = replacementsToEdits(*Code, *Replacements); + WorkspaceEdit WE; + WE.changes = {{Params.textDocument.uri.uri(), Edits}}; + reply(WE); + }); } void ClangdLSPServer::onDocumentDidClose(DidCloseTextDocumentParams &Params) { - Server.removeDocument(Params.textDocument.uri.file); + Server.removeDocument(Params.textDocument.uri.file()); } void ClangdLSPServer::onDocumentOnTypeFormatting( DocumentOnTypeFormattingParams &Params) { - auto File = Params.textDocument.uri.file; + auto File = Params.textDocument.uri.file(); auto Code = Server.getDocument(File); if (!Code) return replyError(ErrorCode::InvalidParams, @@ -221,7 +260,7 @@ void ClangdLSPServer::onDocumentRangeFormatting( DocumentRangeFormattingParams &Params) { - auto File = Params.textDocument.uri.file; + auto File = Params.textDocument.uri.file(); auto Code = Server.getDocument(File); if (!Code) return replyError(ErrorCode::InvalidParams, @@ -236,7 +275,7 @@ } void ClangdLSPServer::onDocumentFormatting(DocumentFormattingParams &Params) { - auto File = Params.textDocument.uri.file; + auto File = Params.textDocument.uri.file(); auto Code = Server.getDocument(File); if (!Code) return replyError(ErrorCode::InvalidParams, @@ -253,14 +292,14 @@ void ClangdLSPServer::onCodeAction(CodeActionParams &Params) { // We provide a code action for each diagnostic at the requested location // which has FixIts available. - auto Code = Server.getDocument(Params.textDocument.uri.file); + auto Code = Server.getDocument(Params.textDocument.uri.file()); if (!Code) return replyError(ErrorCode::InvalidParams, "onCodeAction called for non-added file"); json::ary Commands; for (Diagnostic &D : Params.context.diagnostics) { - auto Edits = getFixIts(Params.textDocument.uri.file, D); + auto Edits = getFixIts(Params.textDocument.uri.file(), D); if (!Edits.empty()) { WorkspaceEdit WE; WE.changes = {{Params.textDocument.uri.uri(), std::move(Edits)}}; @@ -275,44 +314,71 @@ } void ClangdLSPServer::onCompletion(TextDocumentPositionParams &Params) { - Server.codeComplete(Params.textDocument.uri.file, Params.position, CCOpts, + Server.codeComplete(Params.textDocument.uri.file(), Params.position, CCOpts, [](Tagged List) { reply(List.Value); }); } void ClangdLSPServer::onSignatureHelp(TextDocumentPositionParams &Params) { - auto SignatureHelp = - Server.signatureHelp(Params.textDocument.uri.file, Params.position); - if (!SignatureHelp) - return replyError(ErrorCode::InvalidParams, - llvm::toString(SignatureHelp.takeError())); - reply(SignatureHelp->Value); + Server.signatureHelp(Params.textDocument.uri.file(), Params.position, + [](llvm::Expected> SignatureHelp) { + if (!SignatureHelp) + return replyError( + ErrorCode::InvalidParams, + llvm::toString(SignatureHelp.takeError())); + reply(SignatureHelp->Value); + }); } void ClangdLSPServer::onGoToDefinition(TextDocumentPositionParams &Params) { - auto Items = - Server.findDefinitions(Params.textDocument.uri.file, Params.position); - if (!Items) - return replyError(ErrorCode::InvalidParams, - llvm::toString(Items.takeError())); - reply(json::ary(Items->Value)); + Server.findDefinitions( + Params.textDocument.uri.file(), Params.position, + [](llvm::Expected>> Items) { + if (!Items) + return replyError(ErrorCode::InvalidParams, + llvm::toString(Items.takeError())); + reply(json::ary(Items->Value)); + }); } void ClangdLSPServer::onSwitchSourceHeader(TextDocumentIdentifier &Params) { - llvm::Optional Result = Server.switchSourceHeader(Params.uri.file); + llvm::Optional Result = Server.switchSourceHeader(Params.uri.file()); reply(Result ? URI::createFile(*Result).toString() : ""); } void ClangdLSPServer::onDocumentHighlight(TextDocumentPositionParams &Params) { - auto Highlights = Server.findDocumentHighlights(Params.textDocument.uri.file, - Params.position); + Server.findDocumentHighlights( + Params.textDocument.uri.file(), Params.position, + [](llvm::Expected>> Highlights) { + if (!Highlights) + return replyError(ErrorCode::InternalError, + llvm::toString(Highlights.takeError())); + reply(json::ary(Highlights->Value)); + }); +} - if (!Highlights) { - replyError(ErrorCode::InternalError, - llvm::toString(Highlights.takeError())); - return; - } +void ClangdLSPServer::onHover(TextDocumentPositionParams &Params) { + Server.findHover(Params.textDocument.uri.file(), Params.position, + [](llvm::Expected> H) { + if (!H) { + replyError(ErrorCode::InternalError, + llvm::toString(H.takeError())); + return; + } + + reply(H->Value); + }); +} + +// FIXME: This function needs to be properly tested. +void ClangdLSPServer::onChangeConfiguration( + DidChangeConfigurationParams &Params) { + ClangdConfigurationParamsChange &Settings = Params.settings; - reply(json::ary(Highlights->Value)); + // Compilation database change. + if (Settings.compilationDatabasePath.hasValue()) { + CDB.setCompileCommandsDir(Settings.compilationDatabasePath.getValue()); + Server.reparseOpenedFiles(); + } } ClangdLSPServer::ClangdLSPServer(JSONOutput &Out, unsigned AsyncThreadsCount, Index: clangd/ClangdServer.h =================================================================== --- clangd/ClangdServer.h +++ clangd/ClangdServer.h @@ -162,6 +162,11 @@ /// and AST and rebuild them from scratch. void forceReparse(PathRef File); + /// Calls forceReparse() on all currently opened files. + /// As a result, this method may be very expensive. + /// This method is normally called when the compilation database is changed. + void reparseOpenedFiles(); + /// Run code completion for \p File at \p Pos. /// Request is processed asynchronously. /// @@ -189,22 +194,32 @@ /// will be used. If \p UsedFS is non-null, it will be overwritten by /// vfs::FileSystem used for signature help. This method should only be called /// for currently tracked files. - llvm::Expected> - signatureHelp(PathRef File, Position Pos, - llvm::Optional OverridenContents = llvm::None, - IntrusiveRefCntPtr *UsedFS = nullptr); + void signatureHelp( + PathRef File, Position Pos, + UniqueFunction>)> Callback, + llvm::Optional OverridenContents = llvm::None, + IntrusiveRefCntPtr *UsedFS = nullptr); /// Get definition of symbol at a specified \p Line and \p Column in \p File. - llvm::Expected>> findDefinitions(PathRef File, - Position Pos); + void findDefinitions( + PathRef File, Position Pos, + UniqueFunction>>)> + Callback); /// Helper function that returns a path to the corresponding source file when /// given a header file and vice versa. llvm::Optional switchSourceHeader(PathRef Path); /// Get document highlights for a given position. - llvm::Expected>> - findDocumentHighlights(PathRef File, Position Pos); + void findDocumentHighlights( + PathRef File, Position Pos, + UniqueFunction< + void(llvm::Expected>>)> + Callback); + + /// Get code hover for a given position. + void findHover(PathRef File, Position Pos, + UniqueFunction>)> Callback); /// Run formatting for \p Rng inside \p File with content \p Code. llvm::Expected formatRange(StringRef Code, @@ -221,8 +236,16 @@ /// Rename all occurrences of the symbol at the \p Pos in \p File to /// \p NewName. - Expected> rename(PathRef File, Position Pos, - llvm::StringRef NewName); + void rename(PathRef File, Position Pos, llvm::StringRef NewName, + UniqueFunction>)> + Callback); + + /// Inserts a new #include of \p Header into \p File, if it's not present. + /// \p Header is either an URI that can be resolved to an #include path that + /// is suitable to be inserted or a literal string quoted with <> or "" that + /// can be #included directly. + Expected insertInclude(PathRef File, StringRef Code, + StringRef Header); /// Gets current document contents for \p File. Returns None if \p File is not /// currently tracked. @@ -233,7 +256,7 @@ /// Only for testing purposes. /// Waits until all requests to worker thread are finished and dumps AST for /// \p File. \p File must be in the list of added documents. - std::string dumpAST(PathRef File); + void dumpAST(PathRef File, UniqueFunction Callback); /// Called when an event occurs for a watched file in the workspace. void onFileEvent(const DidChangeWatchedFilesParams &Params); Index: clangd/ClangdServer.cpp =================================================================== --- clangd/ClangdServer.cpp +++ clangd/ClangdServer.cpp @@ -9,6 +9,7 @@ #include "ClangdServer.h" #include "CodeComplete.h" +#include "Headers.h" #include "SourceCode.h" #include "XRefs.h" #include "index/Merge.h" @@ -31,36 +32,6 @@ namespace { -// Issues an async read of AST and waits for results. -template -Ret blockingRunWithAST(TUScheduler &S, PathRef File, Func &&F) { - // Using shared_ptr to workaround MSVC bug. It requires future<> arguments to - // have default and copy ctor. - auto SharedPtrFunc = [&](llvm::Expected Arg) { - return std::make_shared(F(std::move(Arg))); - }; - std::packaged_task(llvm::Expected)> Task( - SharedPtrFunc); - auto Future = Task.get_future(); - S.runWithAST(File, std::move(Task)); - return std::move(*Future.get()); -} - -// Issues an async read of preamble and waits for results. -template -Ret blockingRunWithPreamble(TUScheduler &S, PathRef File, Func &&F) { - // Using shared_ptr to workaround MSVC bug. It requires future<> arguments to - // have default and copy ctor. - auto SharedPtrFunc = [&](llvm::Expected Arg) { - return std::make_shared(F(std::move(Arg))); - }; - std::packaged_task(llvm::Expected)> - Task(SharedPtrFunc); - auto Future = Task.get_future(); - S.runWithPreamble(File, std::move(Task)); - return std::move(*Future.get()); -} - void ignoreError(llvm::Error Err) { handleAllErrors(std::move(Err), [](const llvm::ErrorInfoBase &) {}); } @@ -208,15 +179,17 @@ Callback(make_tagged(std::move(Result), std::move(TaggedFS.Tag))); }; - WorkScheduler.runWithPreamble(File, BindWithForward(Task, std::move(Contents), - File.str(), - std::move(Callback))); + WorkScheduler.runWithPreamble("CodeComplete", File, + BindWithForward(Task, std::move(Contents), + File.str(), + std::move(Callback))); } -llvm::Expected> -ClangdServer::signatureHelp(PathRef File, Position Pos, - llvm::Optional OverridenContents, - IntrusiveRefCntPtr *UsedFS) { +void ClangdServer::signatureHelp( + PathRef File, Position Pos, + UniqueFunction>)> Callback, + llvm::Optional OverridenContents, + IntrusiveRefCntPtr *UsedFS) { auto TaggedFS = FSProvider.getTaggedFileSystem(File); if (UsedFS) *UsedFS = TaggedFS.Value; @@ -227,27 +200,31 @@ } else { VersionedDraft Latest = DraftMgr.getDraft(File); if (!Latest.Draft) - return llvm::make_error( + return Callback(llvm::make_error( "signatureHelp is called for non-added document", - llvm::errc::invalid_argument); + llvm::errc::invalid_argument)); Contents = std::move(*Latest.Draft); } - auto Action = [=](llvm::Expected IP) - -> Expected> { + auto PCHs = this->PCHs; + auto Action = [Contents, Pos, TaggedFS, + PCHs](Path File, decltype(Callback) Callback, + llvm::Expected IP) { if (!IP) - return IP.takeError(); + return Callback(IP.takeError()); + auto PreambleData = IP->Preamble; auto &Command = IP->Inputs.CompileCommand; - - return make_tagged( + Callback(make_tagged( clangd::signatureHelp(File, Command, PreambleData ? &PreambleData->Preamble : nullptr, Contents, Pos, TaggedFS.Value, PCHs), - TaggedFS.Tag); + TaggedFS.Tag)); }; - return blockingRunWithPreamble>>(WorkScheduler, - File, Action); + + WorkScheduler.runWithPreamble( + "SignatureHelp", File, + BindWithForward(Action, File.str(), std::move(Callback))); } llvm::Expected @@ -276,12 +253,15 @@ return formatCode(Code, File, {tooling::Range(PreviousLBracePos, Len)}); } -Expected> -ClangdServer::rename(PathRef File, Position Pos, llvm::StringRef NewName) { - using RetType = Expected>; - auto Action = [=](Expected InpAST) -> RetType { +void ClangdServer::rename( + PathRef File, Position Pos, llvm::StringRef NewName, + UniqueFunction>)> + Callback) { + auto Action = [Pos](Path File, std::string NewName, + decltype(Callback) Callback, + Expected InpAST) { if (!InpAST) - return InpAST.takeError(); + return Callback(InpAST.takeError()); auto &AST = InpAST->AST; RefactoringResultCollector ResultCollector; @@ -289,23 +269,24 @@ const FileEntry *FE = SourceMgr.getFileEntryForID(SourceMgr.getMainFileID()); if (!FE) - return llvm::make_error( - "rename called for non-added document", llvm::errc::invalid_argument); + return Callback(llvm::make_error( + "rename called for non-added document", + llvm::errc::invalid_argument)); SourceLocation SourceLocationBeg = clangd::getBeginningOfIdentifier(AST, Pos, FE); tooling::RefactoringRuleContext Context( AST.getASTContext().getSourceManager()); Context.setASTContext(AST.getASTContext()); auto Rename = clang::tooling::RenameOccurrences::initiate( - Context, SourceRange(SourceLocationBeg), NewName.str()); + Context, SourceRange(SourceLocationBeg), NewName); if (!Rename) - return Rename.takeError(); + return Callback(Rename.takeError()); Rename->invoke(ResultCollector, Context); assert(ResultCollector.Result.hasValue()); if (!ResultCollector.Result.getValue()) - return ResultCollector.Result->takeError(); + return Callback(ResultCollector.Result->takeError()); std::vector Replacements; for (const tooling::AtomicChange &Change : ResultCollector.Result->get()) { @@ -324,9 +305,51 @@ Replacements.push_back(Rep); } } - return Replacements; + return Callback(Replacements); }; - return blockingRunWithAST(WorkScheduler, File, std::move(Action)); + + WorkScheduler.runWithAST( + "Rename", File, + BindWithForward(Action, File.str(), NewName.str(), std::move(Callback))); +} + +Expected +ClangdServer::insertInclude(PathRef File, StringRef Code, + llvm::StringRef Header) { + std::string ToInclude; + if (Header.startswith("<") || Header.startswith("\"")) { + ToInclude = Header; + } else { + auto U = URI::parse(Header); + if (!U) + return U.takeError(); + auto Resolved = URI::resolve(*U, /*HintPath=*/File); + if (!Resolved) + return Resolved.takeError(); + + tooling::CompileCommand CompileCommand = + CompileArgs.getCompileCommand(File); + auto Include = + calculateIncludePath(File, Code, *Resolved, CompileCommand, + FSProvider.getTaggedFileSystem(File).Value); + if (!Include) + return Include.takeError(); + if (Include->empty()) + return tooling::Replacements(); + ToInclude = std::move(*Include); + } + + auto Style = format::getStyle("file", File, "llvm"); + if (!Style) { + llvm::consumeError(Style.takeError()); + // FIXME(ioeric): needs more consistent style support in clangd server. + Style = format::getLLVMStyle(); + } + // Replacement with offset UINT_MAX and length 0 will be treated as include + // insertion. + tooling::Replacement R(File, /*Offset=*/UINT_MAX, 0, "#include " + ToInclude); + return format::cleanupAroundReplacements(Code, tooling::Replacements(R), + *Style); } llvm::Optional ClangdServer::getDocument(PathRef File) { @@ -336,37 +359,42 @@ return std::move(*Latest.Draft); } -std::string ClangdServer::dumpAST(PathRef File) { - auto Action = [](llvm::Expected InpAST) -> std::string { +void ClangdServer::dumpAST(PathRef File, + UniqueFunction Callback) { + auto Action = [](decltype(Callback) Callback, + llvm::Expected InpAST) { if (!InpAST) { ignoreError(InpAST.takeError()); - return ""; + return Callback(""); } - std::string Result; llvm::raw_string_ostream ResultOS(Result); clangd::dumpAST(InpAST->AST, ResultOS); ResultOS.flush(); - return Result; + Callback(Result); }; - return blockingRunWithAST(WorkScheduler, File, - std::move(Action)); + + WorkScheduler.runWithAST("DumpAST", File, + BindWithForward(Action, std::move(Callback))); } -llvm::Expected>> -ClangdServer::findDefinitions(PathRef File, Position Pos) { +void ClangdServer::findDefinitions( + PathRef File, Position Pos, + UniqueFunction>>)> + Callback) { auto TaggedFS = FSProvider.getTaggedFileSystem(File); - - using RetType = llvm::Expected>>; - auto Action = [=](llvm::Expected InpAST) -> RetType { + auto Action = [Pos, TaggedFS](decltype(Callback) Callback, + llvm::Expected InpAST) { if (!InpAST) - return InpAST.takeError(); + return Callback(InpAST.takeError()); auto Result = clangd::findDefinitions(InpAST->AST, Pos); - return make_tagged(std::move(Result), TaggedFS.Tag); + Callback(make_tagged(std::move(Result), TaggedFS.Tag)); }; - return blockingRunWithAST(WorkScheduler, File, Action); + + WorkScheduler.runWithAST("Definitions", File, + BindWithForward(Action, std::move(Callback))); } llvm::Optional ClangdServer::switchSourceHeader(PathRef Path) { @@ -442,24 +470,52 @@ } } -llvm::Expected>> -ClangdServer::findDocumentHighlights(PathRef File, Position Pos) { +void ClangdServer::findDocumentHighlights( + PathRef File, Position Pos, + UniqueFunction>>)> + Callback) { auto FileContents = DraftMgr.getDraft(File); if (!FileContents.Draft) - return llvm::make_error( + return Callback(llvm::make_error( "findDocumentHighlights called on non-added file", - llvm::errc::invalid_argument); + llvm::errc::invalid_argument)); auto TaggedFS = FSProvider.getTaggedFileSystem(File); - using RetType = llvm::Expected>>; - auto Action = [=](llvm::Expected InpAST) -> RetType { + auto Action = [TaggedFS, Pos](decltype(Callback) Callback, + llvm::Expected InpAST) { if (!InpAST) - return InpAST.takeError(); + return Callback(InpAST.takeError()); auto Result = clangd::findDocumentHighlights(InpAST->AST, Pos); - return make_tagged(std::move(Result), TaggedFS.Tag); + Callback(make_tagged(std::move(Result), TaggedFS.Tag)); + }; + + WorkScheduler.runWithAST("Highlights", File, + BindWithForward(Action, std::move(Callback))); +} + +void ClangdServer::findHover( + PathRef File, Position Pos, + UniqueFunction>)> Callback) { + Hover FinalHover; + auto FileContents = DraftMgr.getDraft(File); + if (!FileContents.Draft) + return Callback(llvm::make_error( + "findHover called on non-added file", llvm::errc::invalid_argument)); + + auto TaggedFS = FSProvider.getTaggedFileSystem(File); + + auto Action = [Pos, TaggedFS](decltype(Callback) Callback, + llvm::Expected InpAST) { + if (!InpAST) + return Callback(InpAST.takeError()); + + Hover Result = clangd::getHover(InpAST->AST, Pos); + Callback(make_tagged(std::move(Result), TaggedFS.Tag)); }; - return blockingRunWithAST(WorkScheduler, File, Action); + + WorkScheduler.runWithAST("Hover", File, + BindWithForward(Action, std::move(Callback))); } void ClangdServer::scheduleReparseAndDiags( @@ -500,6 +556,11 @@ std::move(Callback)); } +void ClangdServer::reparseOpenedFiles() { + for (const Path &FilePath : DraftMgr.getActiveFiles()) + forceReparse(FilePath); +} + void ClangdServer::onFileEvent(const DidChangeWatchedFilesParams &Params) { // FIXME: Do nothing for now. This will be used for indexing and potentially // invalidating other caches. Index: clangd/ClangdUnit.h =================================================================== --- clangd/ClangdUnit.h +++ clangd/ClangdUnit.h @@ -152,12 +152,15 @@ /// This method is const to ensure we don't incidentally modify any fields. std::shared_ptr rebuildPreamble(CompilerInvocation &CI, + const tooling::CompileCommand &Command, IntrusiveRefCntPtr FS, llvm::MemoryBuffer &ContentsBuffer) const; - Path FileName; - bool StorePreamblesInMemory; + const Path FileName; + const bool StorePreamblesInMemory; + /// The last CompileCommand used to build AST and Preamble. + tooling::CompileCommand Command; /// The last parsed AST. llvm::Optional AST; /// The last built Preamble. Index: clangd/ClangdUnit.cpp =================================================================== --- clangd/ClangdUnit.cpp +++ clangd/ClangdUnit.cpp @@ -34,6 +34,13 @@ namespace { +bool compileCommandsAreEqual(const tooling::CompileCommand &LHS, + const tooling::CompileCommand &RHS) { + // We don't check for Output, it should not matter to clangd. + return LHS.Directory == RHS.Directory && LHS.Filename == RHS.Filename && + llvm::makeArrayRef(LHS.CommandLine).equals(RHS.CommandLine); +} + template std::size_t getUsedBytes(const std::vector &Vec) { return Vec.capacity() * sizeof(T); } @@ -417,7 +424,7 @@ // Compute updated Preamble. std::shared_ptr NewPreamble = - rebuildPreamble(*CI, Inputs.FS, *ContentsBuffer); + rebuildPreamble(*CI, Inputs.CompileCommand, Inputs.FS, *ContentsBuffer); // Remove current AST to avoid wasting memory. AST = llvm::None; @@ -439,10 +446,13 @@ Diagnostics.insert(Diagnostics.end(), NewAST->getDiagnostics().begin(), NewAST->getDiagnostics().end()); } - if (ASTCallback && NewAST) + if (ASTCallback && NewAST) { + trace::Span Tracer("Running ASTCallback"); ASTCallback(FileName, NewAST.getPointer()); + } // Write the results of rebuild into class fields. + Command = std::move(Inputs.CompileCommand); Preamble = std::move(NewPreamble); AST = std::move(NewAST); return Diagnostics; @@ -469,11 +479,12 @@ std::shared_ptr CppFile::rebuildPreamble(CompilerInvocation &CI, + const tooling::CompileCommand &Command, IntrusiveRefCntPtr FS, llvm::MemoryBuffer &ContentsBuffer) const { const auto &OldPreamble = this->Preamble; auto Bounds = ComputePreambleBounds(*CI.getLangOpts(), &ContentsBuffer, 0); - if (OldPreamble && + if (OldPreamble && compileCommandsAreEqual(this->Command, Command) && OldPreamble->Preamble.CanReuse(CI, &ContentsBuffer, Bounds, FS.get())) { log("Reusing preamble for file " + Twine(FileName)); return OldPreamble; Index: clangd/CodeComplete.cpp =================================================================== --- clangd/CodeComplete.cpp +++ clangd/CodeComplete.cpp @@ -19,13 +19,16 @@ #include "Compiler.h" #include "FuzzyMatch.h" #include "Logger.h" +#include "SourceCode.h" #include "Trace.h" #include "index/Index.h" +#include "clang/Format/Format.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendActions.h" #include "clang/Index/USRGeneration.h" #include "clang/Sema/CodeCompleteConsumer.h" #include "clang/Sema/Sema.h" +#include "clang/Tooling/Core/Replacement.h" #include "llvm/Support/Format.h" #include @@ -249,7 +252,8 @@ } // Builds an LSP completion item. - CompletionItem build(const CompletionItemScores &Scores, + CompletionItem build(llvm::StringRef FileName, + const CompletionItemScores &Scores, const CodeCompleteOptions &Opts, CodeCompletionString *SemaCCS) const { assert(bool(SemaResult) == bool(SemaCCS)); @@ -282,6 +286,28 @@ I.documentation = D->Documentation; if (I.detail.empty()) I.detail = D->CompletionDetail; + // We only insert #include for items with details, since we can't tell + // whether the file URI of the canonical declaration would be the + // canonical #include without checking IncludeHeader in the detail. + // FIXME: delay creating include insertion command to + // "completionItem/resolve", when it is supported + if (!D->IncludeHeader.empty() || + !IndexResult->CanonicalDeclaration.FileURI.empty()) { + // LSP favors additionalTextEdits over command. But we are still using + // command here because it would be expensive to calculate #include + // insertion edits for all candidates, and the include insertion edit + // is unlikely to conflict with the code completion edits. + Command Cmd; + // Command title is not added since this is not a user-facing command. + Cmd.command = ExecuteCommandParams::CLANGD_INSERT_HEADER_INCLUDE; + IncludeInsertion Insertion; + Insertion.header = D->IncludeHeader.empty() + ? IndexResult->CanonicalDeclaration.FileURI + : D->IncludeHeader; + Insertion.textDocument.uri = URIForFile(FileName); + Cmd.includeInsertion = std::move(Insertion); + I.command = std::move(Cmd); + } } } I.scoreInfo = Scores; @@ -806,6 +832,7 @@ // This score is combined with the result quality score for the final score. // - TopN determines the results with the best score. class CodeCompleteFlow { + PathRef FileName; const CodeCompleteOptions &Opts; // Sema takes ownership of Recorder. Recorder is valid until Sema cleanup. std::unique_ptr RecorderOwner; @@ -816,9 +843,9 @@ public: // A CodeCompleteFlow object is only useful for calling run() exactly once. - CodeCompleteFlow(const CodeCompleteOptions &Opts) - : Opts(Opts), RecorderOwner(new CompletionRecorder(Opts)), - Recorder(*RecorderOwner) {} + CodeCompleteFlow(PathRef FileName, const CodeCompleteOptions &Opts) + : FileName(FileName), Opts(Opts), + RecorderOwner(new CompletionRecorder(Opts)), Recorder(*RecorderOwner) {} CompletionList run(const SemaCompleteInput &SemaCCInput) && { trace::Span Tracer("CodeCompleteFlow"); @@ -828,9 +855,12 @@ CompletionList Output; semaCodeComplete(std::move(RecorderOwner), Opts.getClangCompleteOpts(), SemaCCInput, [&] { - if (Recorder.CCSema) + if (Recorder.CCSema) { Output = runWithSema(); - else + SPAN_ATTACH( + Tracer, "sema_completion_kind", + getCompletionKindString(Recorder.CCContext.getKind())); + } else log("Code complete: no Sema callback, 0 results"); }); @@ -889,8 +919,9 @@ Req.Query, llvm::join(Req.Scopes.begin(), Req.Scopes.end(), ","))); // Run the query against the index. - Incomplete |= !Opts.Index->fuzzyFind( - Req, [&](const Symbol &Sym) { ResultsBuilder.insert(Sym); }); + if (Opts.Index->fuzzyFind( + Req, [&](const Symbol &Sym) { ResultsBuilder.insert(Sym); })) + Incomplete = true; return std::move(ResultsBuilder).build(); } @@ -948,7 +979,8 @@ NSema += bool(SemaResult); NIndex += bool(IndexResult); NBoth += SemaResult && IndexResult; - Incomplete |= Candidates.push({C, Scores}); + if (Candidates.push({C, Scores})) + Incomplete = true; } CompletionItem toCompletionItem(const CompletionCandidate &Candidate, @@ -956,7 +988,7 @@ CodeCompletionString *SemaCCS = nullptr; if (auto *SR = Candidate.SemaResult) SemaCCS = Recorder.codeCompletionString(*SR, Opts.IncludeBriefComments); - return Candidate.build(Scores, Opts, SemaCCS); + return Candidate.build(FileName, Scores, Opts, SemaCCS); } }; @@ -967,8 +999,8 @@ IntrusiveRefCntPtr VFS, std::shared_ptr PCHs, CodeCompleteOptions Opts) { - return CodeCompleteFlow(Opts).run( - {FileName, Command, Preamble, Contents, Pos, VFS, PCHs}); + return CodeCompleteFlow(FileName, Opts) + .run({FileName, Command, Preamble, Contents, Pos, VFS, PCHs}); } SignatureHelp signatureHelp(PathRef FileName, Index: clangd/DraftStore.h =================================================================== --- clangd/DraftStore.h +++ clangd/DraftStore.h @@ -40,6 +40,12 @@ /// \return version and contents of the stored document. /// For untracked files, a (0, None) pair is returned. VersionedDraft getDraft(PathRef File) const; + + /// \return List of names of active drafts in this store. Drafts that were + /// removed (which still have an entry in the Drafts map) are not returned by + /// this function. + std::vector getActiveFiles() const; + /// \return version of the tracked document. /// For untracked files, 0 is returned. DocVersion getVersion(PathRef File) const; Index: clangd/DraftStore.cpp =================================================================== --- clangd/DraftStore.cpp +++ clangd/DraftStore.cpp @@ -21,6 +21,17 @@ return It->second; } +std::vector DraftStore::getActiveFiles() const { + std::lock_guard Lock(Mutex); + std::vector ResultVector; + + for (auto DraftIt = Drafts.begin(); DraftIt != Drafts.end(); DraftIt++) + if (DraftIt->second.Draft) + ResultVector.push_back(DraftIt->getKey()); + + return ResultVector; +} + DocVersion DraftStore::getVersion(PathRef File) const { std::lock_guard Lock(Mutex); Index: clangd/GlobalCompilationDatabase.h =================================================================== --- clangd/GlobalCompilationDatabase.h +++ clangd/GlobalCompilationDatabase.h @@ -61,6 +61,9 @@ /// Uses the default fallback command, adding any extra flags. tooling::CompileCommand getFallbackCommand(PathRef File) const override; + /// Set the compile commands directory to \p P. + void setCompileCommandsDir(Path P); + /// Sets the extra flags that should be added to a file. void setExtraFlagsForFile(PathRef File, std::vector ExtraFlags); Index: clangd/GlobalCompilationDatabase.cpp =================================================================== --- clangd/GlobalCompilationDatabase.cpp +++ clangd/GlobalCompilationDatabase.cpp @@ -51,6 +51,12 @@ return C; } +void DirectoryBasedGlobalCompilationDatabase::setCompileCommandsDir(Path P) { + std::lock_guard Lock(Mutex); + CompileCommandsDir = P; + CompilationDatabases.clear(); +} + void DirectoryBasedGlobalCompilationDatabase::setExtraFlagsForFile( PathRef File, std::vector ExtraFlags) { std::lock_guard Lock(Mutex); Index: clangd/Headers.h =================================================================== --- /dev/null +++ clangd/Headers.h @@ -0,0 +1,40 @@ +//===--- Headers.h - Include headers -----------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_HEADERS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_HEADERS_H + +#include "Path.h" +#include "clang/Basic/VirtualFileSystem.h" +#include "clang/Tooling/CompilationDatabase.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Error.h" + +namespace clang { +namespace clangd { + +/// Determines the preferred way to #include a file, taking into account the +/// search path. Usually this will prefer a shorter representation like +/// 'Foo/Bar.h' over a longer one like 'Baz/include/Foo/Bar.h'. +/// +/// \param File is an absolute file path. +/// \param Header is an absolute file path. +/// \return A quoted "path" or . This returns an empty string if: +/// - \p Header is already (directly) included in the file (including those +/// included via different paths). +/// - \p Header is the same as \p File. +llvm::Expected +calculateIncludePath(PathRef File, llvm::StringRef Code, llvm::StringRef Header, + const tooling::CompileCommand &CompileCommand, + IntrusiveRefCntPtr FS); + +} // namespace clangd +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_HEADERS_H Index: clangd/Headers.cpp =================================================================== --- /dev/null +++ clangd/Headers.cpp @@ -0,0 +1,125 @@ +//===--- Headers.cpp - Include headers ---------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "Headers.h" +#include "Compiler.h" +#include "Logger.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/CompilerInvocation.h" +#include "clang/Frontend/FrontendActions.h" +#include "clang/Lex/HeaderSearch.h" +#include "clang/Lex/PreprocessorOptions.h" +#include "clang/Tooling/CompilationDatabase.h" +#include "llvm/Support/Path.h" + +namespace clang { +namespace clangd { +namespace { + +class RecordHeaders : public PPCallbacks { +public: + RecordHeaders(std::set &Headers) : Headers(Headers) {} + + void InclusionDirective(SourceLocation /*HashLoc*/, + const Token & /*IncludeTok*/, + llvm::StringRef /*FileName*/, bool /*IsAngled*/, + CharSourceRange /*FilenameRange*/, + const FileEntry *File, llvm::StringRef /*SearchPath*/, + llvm::StringRef /*RelativePath*/, + const Module * /*Imported*/) override { + if (File != nullptr && !File->tryGetRealPathName().empty()) + Headers.insert(File->tryGetRealPathName()); + } + +private: + std::set &Headers; +}; + +} // namespace + +/// FIXME(ioeric): we might not want to insert an absolute include path if the +/// path is not shortened. +llvm::Expected +calculateIncludePath(llvm::StringRef File, llvm::StringRef Code, + llvm::StringRef Header, + const tooling::CompileCommand &CompileCommand, + IntrusiveRefCntPtr FS) { + assert(llvm::sys::path::is_absolute(File) && + llvm::sys::path::is_absolute(Header)); + + if (File == Header) + return ""; + FS->setCurrentWorkingDirectory(CompileCommand.Directory); + + // Set up a CompilerInstance and create a preprocessor to collect existing + // #include headers in \p Code. Preprocesor also provides HeaderSearch with + // which we can calculate the shortest include path for \p Header. + std::vector Argv; + for (const auto &S : CompileCommand.CommandLine) + Argv.push_back(S.c_str()); + IgnoringDiagConsumer IgnoreDiags; + auto CI = clang::createInvocationFromCommandLine( + Argv, + CompilerInstance::createDiagnostics(new DiagnosticOptions(), &IgnoreDiags, + false), + FS); + if (!CI) + return llvm::make_error( + "Failed to create a compiler instance for " + File, + llvm::inconvertibleErrorCode()); + CI->getFrontendOpts().DisableFree = false; + // Parse the main file to get all existing #includes in the file, and then we + // can make sure the same header (even with different include path) is not + // added more than once. + CI->getPreprocessorOpts().SingleFileParseMode = true; + + auto Clang = prepareCompilerInstance( + std::move(CI), /*Preamble=*/nullptr, + llvm::MemoryBuffer::getMemBuffer(Code, File), + std::make_shared(), FS, IgnoreDiags); + auto &DiagOpts = Clang->getDiagnosticOpts(); + DiagOpts.IgnoreWarnings = true; + + if (Clang->getFrontendOpts().Inputs.empty()) + return llvm::make_error( + "Empty frontend action inputs empty for file " + File, + llvm::inconvertibleErrorCode()); + PreprocessOnlyAction Action; + if (!Action.BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0])) + return llvm::make_error( + "Failed to begin preprocessor only action for file " + File, + llvm::inconvertibleErrorCode()); + std::set ExistingHeaders; + Clang->getPreprocessor().addPPCallbacks( + llvm::make_unique(ExistingHeaders)); + if (!Action.Execute()) + return llvm::make_error( + "Failed to execute preprocessor only action for file " + File, + llvm::inconvertibleErrorCode()); + if (ExistingHeaders.find(Header) != ExistingHeaders.end()) { + return llvm::make_error( + Header + " is already included in " + File, + llvm::inconvertibleErrorCode()); + } + + auto &HeaderSearchInfo = Clang->getPreprocessor().getHeaderSearchInfo(); + bool IsSystem = false; + std::string Suggested = HeaderSearchInfo.suggestPathToFileForDiagnostics( + Header, CompileCommand.Directory, &IsSystem); + if (IsSystem) + Suggested = "<" + Suggested + ">"; + else + Suggested = "\"" + Suggested + "\""; + + log("Suggested #include for " + Header + " is: " + Suggested); + return Suggested; +} + +} // namespace clangd +} // namespace clang Index: clangd/JSONRPCDispatcher.cpp =================================================================== --- clangd/JSONRPCDispatcher.cpp +++ clangd/JSONRPCDispatcher.cpp @@ -13,6 +13,7 @@ #include "Trace.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringExtras.h" +#include "llvm/Support/Chrono.h" #include "llvm/Support/SourceMgr.h" #include @@ -60,20 +61,19 @@ OS << Message; OS.flush(); - std::lock_guard Guard(StreamMutex); - // Log without headers. - Logs << "--> " << S << '\n'; - Logs.flush(); - - // Emit message with header. - Outs << "Content-Length: " << S.size() << "\r\n\r\n" << S; - Outs.flush(); + { + std::lock_guard Guard(StreamMutex); + Outs << "Content-Length: " << S.size() << "\r\n\r\n" << S; + Outs.flush(); + } + log(llvm::Twine("--> ") + S); } void JSONOutput::log(const Twine &Message) { + llvm::sys::TimePoint<> Timestamp = std::chrono::system_clock::now(); trace::log(Message); std::lock_guard Guard(StreamMutex); - Logs << Message << '\n'; + Logs << llvm::formatv("[{0:%H:%M:%S.%L}] {1}\n", Timestamp, Message); Logs.flush(); } Index: clangd/Protocol.h =================================================================== --- clangd/Protocol.h +++ clangd/Protocol.h @@ -49,12 +49,17 @@ }; struct URIForFile { - std::string file; + URIForFile() = default; + explicit URIForFile(std::string AbsPath); - std::string uri() const { return URI::createFile(file).toString(); } + /// Retrieves absolute path to the file. + llvm::StringRef file() const { return File; } + + explicit operator bool() const { return !File.empty(); } + std::string uri() const { return URI::createFile(File).toString(); } friend bool operator==(const URIForFile &LHS, const URIForFile &RHS) { - return LHS.file == RHS.file; + return LHS.File == RHS.File; } friend bool operator!=(const URIForFile &LHS, const URIForFile &RHS) { @@ -62,8 +67,11 @@ } friend bool operator<(const URIForFile &LHS, const URIForFile &RHS) { - return LHS.file < RHS.file; + return LHS.File < RHS.File; } + +private: + std::string File; }; /// Serialize/deserialize \p URIForFile to/from a string URI. @@ -74,6 +82,7 @@ /// The text document's URI. URIForFile uri; }; +json::Expr toJSON(const TextDocumentIdentifier &); bool fromJSON(const json::Expr &, TextDocumentIdentifier &); struct Position { @@ -179,6 +188,51 @@ using ShutdownParams = NoParams; using ExitParams = NoParams; +struct CompletionItemClientCapabilities { + /// Client supports snippets as insert text. + bool snippetSupport = false; + /// Client supports commit characters on a completion item. + bool commitCharacterSupport = false; + // Client supports the follow content formats for the documentation property. + // The order describes the preferred format of the client. + // NOTE: not used by clangd at the moment. + // std::vector documentationFormat; +}; +bool fromJSON(const json::Expr &, CompletionItemClientCapabilities &); + +struct CompletionClientCapabilities { + /// Whether completion supports dynamic registration. + bool dynamicRegistration = false; + /// The client supports the following `CompletionItem` specific capabilities. + CompletionItemClientCapabilities completionItem; + // NOTE: not used by clangd at the moment. + // llvm::Optional completionItemKind; + + /// The client supports to send additional context information for a + /// `textDocument/completion` request. + bool contextSupport = false; +}; +bool fromJSON(const json::Expr &, CompletionClientCapabilities &); + +// FIXME: most of the capabilities are missing from this struct. Only the ones +// used by clangd are currently there. +struct TextDocumentClientCapabilities { + /// Capabilities specific to the `textDocument/completion` + CompletionClientCapabilities completion; +}; +bool fromJSON(const json::Expr &, TextDocumentClientCapabilities &); + +struct ClientCapabilities { + // Workspace specific client capabilities. + // NOTE: not used by clangd at the moment. + // WorkspaceClientCapabilities workspace; + + // Text document specific client capabilities. + TextDocumentClientCapabilities textDocument; +}; + +bool fromJSON(const json::Expr &, ClientCapabilities &); + struct InitializeParams { /// The process Id of the parent process that started /// the server. Is null if the process has not been started by another @@ -201,8 +255,7 @@ // initializationOptions?: any; /// The capabilities provided by the client (editor or tool) - /// Note: Not currently used by clangd - // ClientCapabilities capabilities; + ClientCapabilities capabilities; /// The initial trace setting. If omitted trace is disabled ('off'). llvm::Optional trace; @@ -265,6 +318,20 @@ }; bool fromJSON(const json::Expr &, DidChangeWatchedFilesParams &); +/// Clangd extension to manage a workspace/didChangeConfiguration notification +/// since the data received is described as 'any' type in LSP. +struct ClangdConfigurationParamsChange { + llvm::Optional compilationDatabasePath; +}; +bool fromJSON(const json::Expr &, ClangdConfigurationParamsChange &); + +struct DidChangeConfigurationParams { + // We use this predefined struct because it is easier to use + // than the protocol specified type of 'any'. + ClangdConfigurationParamsChange settings; +}; +bool fromJSON(const json::Expr &, DidChangeConfigurationParams &); + struct FormattingOptions { /// Size of a tab in spaces. int tabSize = 0; @@ -372,6 +439,17 @@ bool fromJSON(const json::Expr &, WorkspaceEdit &); json::Expr toJSON(const WorkspaceEdit &WE); +struct IncludeInsertion { + /// The document in which the command was invoked. + TextDocumentIdentifier textDocument; + + /// The header to be inserted. This could be either a URI ir a literal string + /// quoted with <> or "" that can be #included directly. + std::string header; +}; +bool fromJSON(const json::Expr &, IncludeInsertion &); +json::Expr toJSON(const IncludeInsertion &II); + /// Exact commands are not specified in the protocol so we define the /// ones supported by Clangd here. The protocol specifies the command arguments /// to be "any[]" but to make this safer and more manageable, each command we @@ -383,15 +461,25 @@ struct ExecuteCommandParams { // Command to apply fix-its. Uses WorkspaceEdit as argument. const static llvm::StringLiteral CLANGD_APPLY_FIX_COMMAND; + // Command to insert an #include into code. + const static llvm::StringLiteral CLANGD_INSERT_HEADER_INCLUDE; /// The command identifier, e.g. CLANGD_APPLY_FIX_COMMAND std::string command; // Arguments llvm::Optional workspaceEdit; + + llvm::Optional includeInsertion; }; bool fromJSON(const json::Expr &, ExecuteCommandParams &); +struct Command : public ExecuteCommandParams { + std::string title; +}; + +json::Expr toJSON(const Command &C); + struct ApplyWorkspaceEditParams { WorkspaceEdit edit; }; @@ -406,6 +494,27 @@ }; bool fromJSON(const json::Expr &, TextDocumentPositionParams &); +enum class MarkupKind { + PlainText, + Markdown, +}; + +struct MarkupContent { + MarkupKind kind = MarkupKind::PlainText; + std::string value; +}; +json::Expr toJSON(const MarkupContent &MC); + +struct Hover { + /// The hover's content + MarkupContent contents; + + /// An optional range is a range inside a text document + /// that is used to visualize a hover, e.g. by changing the background color. + llvm::Optional range; +}; +json::Expr toJSON(const Hover &H); + /// The kind of a completion entry. enum class CompletionItemKind { Missing = 0, @@ -508,12 +617,10 @@ /// themselves. std::vector additionalTextEdits; + llvm::Optional command; // TODO(krasimir): The following optional fields defined by the language // server protocol are unsupported: // - // command?: Command - An optional command that is executed *after* inserting - // this completion. - // // data?: any - A data entry field that is preserved on a completion item // between a completion and a completion resolve request. }; Index: clangd/Protocol.cpp =================================================================== --- clangd/Protocol.cpp +++ clangd/Protocol.cpp @@ -12,8 +12,8 @@ //===----------------------------------------------------------------------===// #include "Protocol.h" -#include "URI.h" #include "Logger.h" +#include "URI.h" #include "clang/Basic/LLVM.h" #include "llvm/ADT/SmallString.h" #include "llvm/Support/Format.h" @@ -24,6 +24,11 @@ namespace clang { namespace clangd { +URIForFile::URIForFile(std::string AbsPath) { + assert(llvm::sys::path::is_absolute(AbsPath) && "the path is relative"); + File = std::move(AbsPath); +} + bool fromJSON(const json::Expr &E, URIForFile &R) { if (auto S = E.asString()) { auto U = URI::parse(*S); @@ -40,20 +45,22 @@ log(llvm::toString(Path.takeError())); return false; } - R.file = *Path; + R = URIForFile(*Path); return true; } return false; } -json::Expr toJSON(const URIForFile &U) { - return U.uri(); -} +json::Expr toJSON(const URIForFile &U) { return U.uri(); } llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const URIForFile &U) { return OS << U.uri(); } +json::Expr toJSON(const TextDocumentIdentifier &R) { + return json::obj{{"uri", R.uri}}; +} + bool fromJSON(const json::Expr &Params, TextDocumentIdentifier &R) { json::ObjectMapper O(Params); return O && O.map("uri", R.uri); @@ -150,6 +157,41 @@ return false; } +bool fromJSON(const json::Expr &Params, CompletionItemClientCapabilities &R) { + json::ObjectMapper O(Params); + if (!O) + return false; + O.map("snippetSupport", R.snippetSupport); + O.map("commitCharacterSupport", R.commitCharacterSupport); + return true; +} + +bool fromJSON(const json::Expr &Params, CompletionClientCapabilities &R) { + json::ObjectMapper O(Params); + if (!O) + return false; + O.map("dynamicRegistration", R.dynamicRegistration); + O.map("completionItem", R.completionItem); + O.map("contextSupport", R.contextSupport); + return true; +} + +bool fromJSON(const json::Expr &Params, TextDocumentClientCapabilities &R) { + json::ObjectMapper O(Params); + if (!O) + return false; + O.map("completion", R.completion); + return true; +} + +bool fromJSON(const json::Expr &Params, ClientCapabilities &R) { + json::ObjectMapper O(Params); + if (!O) + return false; + O.map("textDocument", R.textDocument); + return true; +} + bool fromJSON(const json::Expr &Params, InitializeParams &R) { json::ObjectMapper O(Params); if (!O) @@ -159,6 +201,7 @@ O.map("processId", R.processId); O.map("rootUri", R.rootUri); O.map("rootPath", R.rootPath); + O.map("capabilities", R.capabilities); O.map("trace", R.trace); // initializationOptions, capabilities unused return true; @@ -287,6 +330,8 @@ const llvm::StringLiteral ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND = "clangd.applyFix"; +const llvm::StringLiteral ExecuteCommandParams::CLANGD_INSERT_HEADER_INCLUDE = + "clangd.insertInclude"; bool fromJSON(const json::Expr &Params, ExecuteCommandParams &R) { json::ObjectMapper O(Params); @@ -297,10 +342,22 @@ if (R.command == ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND) { return Args && Args->size() == 1 && fromJSON(Args->front(), R.workspaceEdit); + } else if (R.command == ExecuteCommandParams::CLANGD_INSERT_HEADER_INCLUDE) { + return Args && Args->size() == 1 && + fromJSON(Args->front(), R.includeInsertion); } return false; // Unrecognized command. } +json::Expr toJSON(const Command &C) { + auto Cmd = json::obj{{"title", C.title}, {"command", C.command}}; + if (C.workspaceEdit) + Cmd["arguments"] = {*C.workspaceEdit}; + else if (C.includeInsertion) + Cmd["arguments"] = {*C.includeInsertion}; + return std::move(Cmd); +} + json::Expr toJSON(const WorkspaceEdit &WE) { if (!WE.changes) return json::obj{}; @@ -310,6 +367,15 @@ return json::obj{{"changes", std::move(FileChanges)}}; } +bool fromJSON(const json::Expr &II, IncludeInsertion &R) { + json::ObjectMapper O(II); + return O && O.map("textDocument", R.textDocument) && + O.map("header", R.header); +} +json::Expr toJSON(const IncludeInsertion &II) { + return json::obj{{"textDocument", II.textDocument}, {"header", II.header}}; +} + json::Expr toJSON(const ApplyWorkspaceEditParams &Params) { return json::obj{{"edit", Params.edit}}; } @@ -320,6 +386,35 @@ O.map("position", R.position); } +static StringRef toTextKind(MarkupKind Kind) { + switch (Kind) { + case MarkupKind::PlainText: + return "plaintext"; + case MarkupKind::Markdown: + return "markdown"; + } + llvm_unreachable("Invalid MarkupKind"); +} + +json::Expr toJSON(const MarkupContent &MC) { + if (MC.value.empty()) + return nullptr; + + return json::obj{ + {"kind", toTextKind(MC.kind)}, + {"value", MC.value}, + }; +} + +json::Expr toJSON(const Hover &H) { + json::obj Result{{"contents", toJSON(H.contents)}}; + + if (H.range.hasValue()) + Result["range"] = toJSON(*H.range); + + return std::move(Result); +} + json::Expr toJSON(const CompletionItem &CI) { assert(!CI.label.empty() && "completion item label is required"); json::obj Result{{"label", CI.label}}; @@ -341,6 +436,8 @@ Result["textEdit"] = *CI.textEdit; if (!CI.additionalTextEdits.empty()) Result["additionalTextEdits"] = json::ary(CI.additionalTextEdits); + if (CI.command) + Result["command"] = *CI.command; return std::move(Result); } @@ -400,5 +497,15 @@ }; } +bool fromJSON(const json::Expr &Params, DidChangeConfigurationParams &CCP) { + json::ObjectMapper O(Params); + return O && O.map("settings", CCP.settings); +} + +bool fromJSON(const json::Expr &Params, ClangdConfigurationParamsChange &CCPC) { + json::ObjectMapper O(Params); + return O && O.map("compilationDatabasePath", CCPC.compilationDatabasePath); +} + } // namespace clangd } // namespace clang Index: clangd/ProtocolHandlers.h =================================================================== --- clangd/ProtocolHandlers.h +++ clangd/ProtocolHandlers.h @@ -51,6 +51,8 @@ virtual void onCommand(ExecuteCommandParams &Params) = 0; virtual void onRename(RenameParams &Parames) = 0; virtual void onDocumentHighlight(TextDocumentPositionParams &Params) = 0; + virtual void onHover(TextDocumentPositionParams &Params) = 0; + virtual void onChangeConfiguration(DidChangeConfigurationParams &Params) = 0; }; void registerCallbackHandlers(JSONRPCDispatcher &Dispatcher, JSONOutput &Out, Index: clangd/ProtocolHandlers.cpp =================================================================== --- clangd/ProtocolHandlers.cpp +++ clangd/ProtocolHandlers.cpp @@ -67,8 +67,11 @@ Register("textDocument/switchSourceHeader", &ProtocolCallbacks::onSwitchSourceHeader); Register("textDocument/rename", &ProtocolCallbacks::onRename); + Register("textDocument/hover", &ProtocolCallbacks::onHover); Register("workspace/didChangeWatchedFiles", &ProtocolCallbacks::onFileEvent); Register("workspace/executeCommand", &ProtocolCallbacks::onCommand); Register("textDocument/documentHighlight", &ProtocolCallbacks::onDocumentHighlight); + Register("workspace/didChangeConfiguration", + &ProtocolCallbacks::onChangeConfiguration); } Index: clangd/TUScheduler.h =================================================================== --- clangd/TUScheduler.h +++ clangd/TUScheduler.h @@ -37,7 +37,7 @@ /// TUScheduler is not thread-safe, only one thread should be providing updates /// and scheduling tasks. /// Callbacks are run on a threadpool and it's appropriate to do slow work in -/// them. +/// them. Each task has a name, used for tracing (should be UpperCamelCase). class TUScheduler { public: TUScheduler(unsigned AsyncThreadsCount, bool StorePreamblesInMemory, @@ -65,7 +65,7 @@ /// \p Action is executed. /// If an error occurs during processing, it is forwarded to the \p Action /// callback. - void runWithAST(PathRef File, + void runWithAST(llvm::StringRef Name, PathRef File, UniqueFunction)> Action); /// Schedule an async read of the Preamble. Preamble passed to \p Action may @@ -74,7 +74,7 @@ /// If an error occurs during processing, it is forwarded to the \p Action /// callback. void runWithPreamble( - PathRef File, + llvm::StringRef Name, PathRef File, UniqueFunction)> Action); /// Wait until there are no scheduled or running tasks. Index: clangd/TUScheduler.cpp =================================================================== --- clangd/TUScheduler.cpp +++ clangd/TUScheduler.cpp @@ -44,8 +44,10 @@ #include "TUScheduler.h" #include "Logger.h" +#include "Trace.h" #include "clang/Frontend/PCHContainerOperations.h" #include "llvm/Support/Errc.h" +#include "llvm/Support/Path.h" #include #include #include @@ -67,7 +69,8 @@ /// worker. class ASTWorker { friend class ASTWorkerHandle; - ASTWorker(Semaphore &Barrier, CppFile AST, bool RunSync); + ASTWorker(llvm::StringRef File, Semaphore &Barrier, CppFile AST, + bool RunSync); public: /// Create a new ASTWorker and return a handle to it. @@ -75,14 +78,15 @@ /// is null, all requests will be processed on the calling thread /// synchronously instead. \p Barrier is acquired when processing each /// request, it is be used to limit the number of actively running threads. - static ASTWorkerHandle Create(AsyncTaskRunner *Tasks, Semaphore &Barrier, - CppFile AST); + static ASTWorkerHandle Create(llvm::StringRef File, AsyncTaskRunner *Tasks, + Semaphore &Barrier, CppFile AST); ~ASTWorker(); void update(ParseInputs Inputs, UniqueFunction>)> OnUpdated); - void runWithAST(UniqueFunction)> Action); + void runWithAST(llvm::StringRef Name, + UniqueFunction)> Action); bool blockUntilIdle(Deadline Timeout) const; std::shared_ptr getPossiblyStalePreamble() const; @@ -96,11 +100,16 @@ /// Signal that run() should finish processing pending requests and exit. void stop(); /// Adds a new task to the end of the request queue. - void startTask(UniqueFunction Task, bool isUpdate, - llvm::Optional CF); + void startTask(llvm::StringRef Name, UniqueFunction Task, + bool isUpdate, llvm::Optional CF); - using RequestWithCtx = std::pair, Context>; + struct Request { + UniqueFunction Action; + std::string Name; + Context Ctx; + }; + std::string File; const bool RunSync; Semaphore &Barrier; // AST and FileInputs are only accessed on the processing thread from run(). @@ -114,7 +123,7 @@ std::size_t LastASTSize; /* GUARDED_BY(Mutex) */ // Set to true to signal run() to finish processing. bool Done; /* GUARDED_BY(Mutex) */ - std::queue Requests; /* GUARDED_BY(Mutex) */ + std::queue Requests; /* GUARDED_BY(Mutex) */ // Only set when last request is an update. This allows us to cancel an update // that was never read, if a subsequent update comes in. llvm::Optional LastUpdateCF; /* GUARDED_BY(Mutex) */ @@ -163,18 +172,21 @@ std::shared_ptr Worker; }; -ASTWorkerHandle ASTWorker::Create(AsyncTaskRunner *Tasks, Semaphore &Barrier, - CppFile AST) { +ASTWorkerHandle ASTWorker::Create(llvm::StringRef File, AsyncTaskRunner *Tasks, + Semaphore &Barrier, CppFile AST) { std::shared_ptr Worker( - new ASTWorker(Barrier, std::move(AST), /*RunSync=*/!Tasks)); + new ASTWorker(File, Barrier, std::move(AST), /*RunSync=*/!Tasks)); if (Tasks) - Tasks->runAsync([Worker]() { Worker->run(); }); + Tasks->runAsync("worker:" + llvm::sys::path::filename(File), + [Worker]() { Worker->run(); }); return ASTWorkerHandle(std::move(Worker)); } -ASTWorker::ASTWorker(Semaphore &Barrier, CppFile AST, bool RunSync) - : RunSync(RunSync), Barrier(Barrier), AST(std::move(AST)), Done(false) { +ASTWorker::ASTWorker(llvm::StringRef File, Semaphore &Barrier, CppFile AST, + bool RunSync) + : File(File), RunSync(RunSync), Barrier(Barrier), AST(std::move(AST)), + Done(false) { if (RunSync) return; } @@ -212,11 +224,12 @@ }; CancellationFlag UpdateCF; - startTask(BindWithForward(Task, UpdateCF, std::move(OnUpdated)), + startTask("Update", BindWithForward(Task, UpdateCF, std::move(OnUpdated)), /*isUpdate=*/true, UpdateCF); } void ASTWorker::runWithAST( + llvm::StringRef Name, UniqueFunction)> Action) { auto Task = [=](decltype(Action) Action) { ParsedAST *ActualAST = AST.getAST(); @@ -233,8 +246,8 @@ LastASTSize = ActualAST->getUsedBytes(); }; - startTask(BindWithForward(Task, std::move(Action)), /*isUpdate=*/false, - llvm::None); + startTask(Name, BindWithForward(Task, std::move(Action)), + /*isUpdate=*/false, llvm::None); } std::shared_ptr @@ -257,13 +270,14 @@ RequestsCV.notify_all(); } -void ASTWorker::startTask(UniqueFunction Task, bool isUpdate, - llvm::Optional CF) { +void ASTWorker::startTask(llvm::StringRef Name, UniqueFunction Task, + bool isUpdate, llvm::Optional CF) { assert(isUpdate == CF.hasValue() && "Only updates are expected to pass CancellationFlag"); if (RunSync) { assert(!Done && "running a task after stop()"); + trace::Span Tracer(Name + ":" + llvm::sys::path::filename(File)); Task(); return; } @@ -281,14 +295,14 @@ } else { LastUpdateCF = llvm::None; } - Requests.emplace(std::move(Task), Context::current().clone()); + Requests.push({std::move(Task), Name, Context::current().clone()}); } // unlock Mutex. RequestsCV.notify_all(); } void ASTWorker::run() { while (true) { - RequestWithCtx Req; + Request Req; { std::unique_lock Lock(Mutex); RequestsCV.wait(Lock, [&]() { return Done || !Requests.empty(); }); @@ -303,9 +317,12 @@ // Leave it on the queue for now, so waiters don't see an empty queue. } // unlock Mutex - std::lock_guard BarrierLock(Barrier); - WithContext Guard(std::move(Req.second)); - Req.first(); + { + std::lock_guard BarrierLock(Barrier); + WithContext Guard(std::move(Req.Ctx)); + trace::Span Tracer(Req.Name); + Req.Action(); + } { std::lock_guard Lock(Mutex); @@ -379,7 +396,7 @@ if (!FD) { // Create a new worker to process the AST-related tasks. ASTWorkerHandle Worker = ASTWorker::Create( - WorkerThreads ? WorkerThreads.getPointer() : nullptr, Barrier, + File, WorkerThreads ? WorkerThreads.getPointer() : nullptr, Barrier, CppFile(File, StorePreamblesInMemory, PCHOps, ASTCallback)); FD = std::unique_ptr(new FileData{Inputs, std::move(Worker)}); } else { @@ -396,7 +413,8 @@ } void TUScheduler::runWithAST( - PathRef File, UniqueFunction)> Action) { + llvm::StringRef Name, PathRef File, + UniqueFunction)> Action) { auto It = Files.find(File); if (It == Files.end()) { Action(llvm::make_error( @@ -405,11 +423,11 @@ return; } - It->second->Worker->runWithAST(std::move(Action)); + It->second->Worker->runWithAST(Name, std::move(Action)); } void TUScheduler::runWithPreamble( - PathRef File, + llvm::StringRef Name, PathRef File, UniqueFunction)> Action) { auto It = Files.find(File); if (It == Files.end()) { @@ -420,6 +438,8 @@ } if (!PreambleTasks) { + trace::Span Tracer(Name); + SPAN_ATTACH(Tracer, "file", File); std::shared_ptr Preamble = It->second->Worker->getPossiblyStalePreamble(); Action(InputsAndPreamble{It->second->Inputs, Preamble.get()}); @@ -428,17 +448,22 @@ ParseInputs InputsCopy = It->second->Inputs; std::shared_ptr Worker = It->second->Worker.lock(); - auto Task = [InputsCopy, Worker, this](Context Ctx, + auto Task = [InputsCopy, Worker, this](std::string Name, std::string File, + Context Ctx, decltype(Action) Action) mutable { std::lock_guard BarrierLock(Barrier); WithContext Guard(std::move(Ctx)); + trace::Span Tracer(Name); + SPAN_ATTACH(Tracer, "file", File); std::shared_ptr Preamble = Worker->getPossiblyStalePreamble(); Action(InputsAndPreamble{InputsCopy, Preamble.get()}); }; PreambleTasks->runAsync( - BindWithForward(Task, Context::current().clone(), std::move(Action))); + "task:" + llvm::sys::path::filename(File), + BindWithForward(Task, std::string(Name), std::string(File), + Context::current().clone(), std::move(Action))); } std::vector> Index: clangd/Threading.h =================================================================== --- clangd/Threading.h +++ clangd/Threading.h @@ -12,6 +12,7 @@ #include "Context.h" #include "Function.h" +#include "llvm/ADT/Twine.h" #include #include #include @@ -81,7 +82,8 @@ void wait() const { (void) wait(llvm::None); } LLVM_NODISCARD bool wait(Deadline D) const; - void runAsync(UniqueFunction Action); + // The name is used for tracing and debugging (e.g. to name a spawned thread). + void runAsync(llvm::Twine Name, UniqueFunction Action); private: mutable std::mutex Mutex; Index: clangd/Threading.cpp =================================================================== --- clangd/Threading.cpp +++ clangd/Threading.cpp @@ -34,7 +34,8 @@ [&] { return InFlightTasks == 0; }); } -void AsyncTaskRunner::runAsync(UniqueFunction Action) { +void AsyncTaskRunner::runAsync(llvm::Twine Name, + UniqueFunction Action) { { std::lock_guard Lock(Mutex); ++InFlightTasks; @@ -51,13 +52,14 @@ }); std::thread( - [](decltype(Action) Action, decltype(CleanupTask)) { + [](std::string Name, decltype(Action) Action, decltype(CleanupTask)) { + llvm::set_thread_name(Name); Action(); // Make sure function stored by Action is destroyed before CleanupTask // is run. Action = nullptr; }, - std::move(Action), std::move(CleanupTask)) + Name.str(), std::move(Action), std::move(CleanupTask)) .detach(); } Index: clangd/Trace.h =================================================================== --- clangd/Trace.h +++ clangd/Trace.h @@ -40,6 +40,12 @@ /// whose destructor records the end of the event. /// The args are *Args, only complete when the event ends. virtual Context beginSpan(llvm::StringRef Name, json::obj *Args) = 0; + // Called when a Span is destroyed (it may still be active on other threads). + // beginSpan() and endSpan() will always form a proper stack on each thread. + // The Context returned by beginSpan is active, but Args is not ready. + // Tracers should not override this unless they need to observe strict + // per-thread nesting. Instead they should observe context destruction. + virtual void endSpan() {}; /// Called for instant events. virtual void instant(llvm::StringRef Name, json::obj &&Args) = 0; @@ -76,7 +82,8 @@ /// SomeJSONExpr is evaluated and copied only if actually needed. class Span { public: - Span(llvm::StringRef Name); + Span(llvm::Twine Name); + ~Span(); /// Mutable metadata, if this span is interested. /// Prefer to use SPAN_ATTACH rather than accessing this directly. Index: clangd/Trace.cpp =================================================================== --- clangd/Trace.cpp +++ clangd/Trace.cpp @@ -16,6 +16,7 @@ #include "llvm/Support/FormatProviders.h" #include "llvm/Support/FormatVariadic.h" #include "llvm/Support/Threading.h" +#include #include namespace clang { @@ -46,40 +47,103 @@ Out.flush(); } + // We stash a Span object in the context. It will record the start/end, + // and this also allows us to look up the parent Span's information. Context beginSpan(llvm::StringRef Name, json::obj *Args) override { - jsonEvent("B", json::obj{{"name", Name}}); - return Context::current().derive(make_scope_exit([this, Args] { - jsonEvent("E", json::obj{{"args", std::move(*Args)}}); - })); + return Context::current().derive( + SpanKey, llvm::make_unique(this, Name, Args)); + } + + // Trace viewer requires each thread to properly stack events. + // So we need to mark only duration that the span was active on the thread. + // (Hopefully any off-thread activity will be connected by a flow event). + // Record the end time here, but don't write the event: Args aren't ready yet. + void endSpan() override { + Context::current().getExisting(SpanKey)->markEnded(); } void instant(llvm::StringRef Name, json::obj &&Args) override { + captureThreadMetadata(); jsonEvent("i", json::obj{{"name", Name}, {"args", std::move(Args)}}); } // Record an event on the current thread. ph, pid, tid, ts are set. // Contents must be a list of the other JSON key/values. - void jsonEvent(StringRef Phase, json::obj &&Contents) { - uint64_t TID = get_threadid(); - std::lock_guard Lock(Mu); - // If we haven't already, emit metadata describing this thread. - if (ThreadsWithMD.insert(TID).second) { - SmallString<32> Name; - get_thread_name(Name); - if (!Name.empty()) { - rawEvent("M", json::obj{ - {"tid", TID}, - {"name", "thread_name"}, - {"args", json::obj{{"name", Name}}}, - }); - } - } - Contents["ts"] = timestamp(); + void jsonEvent(StringRef Phase, json::obj &&Contents, + uint64_t TID = get_threadid(), + double Timestamp = 0) { + Contents["ts"] = Timestamp ? Timestamp : timestamp(); Contents["tid"] = TID; + std::lock_guard Lock(Mu); rawEvent(Phase, std::move(Contents)); } private: + class JSONSpan { + public: + JSONSpan(JSONTracer *Tracer, llvm::StringRef Name, json::obj *Args) + : StartTime(Tracer->timestamp()), EndTime(0), Name(Name), + TID(get_threadid()), Tracer(Tracer), Args(Args) { + // ~JSONSpan() may run in a different thread, so we need to capture now. + Tracer->captureThreadMetadata(); + + // We don't record begin events here (and end events in the destructor) + // because B/E pairs have to appear in the right order, which is awkward. + // Instead we send the complete (X) event in the destructor. + + // If our parent was on a different thread, add an arrow to this span. + auto *Parent = Context::current().get(SpanKey); + if (Parent && *Parent && (*Parent)->TID != TID) { + // If the parent span ended already, then show this as "following" it. + // Otherwise show us as "parallel". + double OriginTime = (*Parent)->EndTime; + if (!OriginTime) + OriginTime = (*Parent)->StartTime; + + auto FlowID = nextID(); + Tracer->jsonEvent("s", + json::obj{{"id", FlowID}, + {"name", "Context crosses threads"}, + {"cat", "dummy"}}, + (*Parent)->TID, (*Parent)->StartTime); + Tracer->jsonEvent("f", + json::obj{{"id", FlowID}, + {"bp", "e"}, + {"name", "Context crosses threads"}, + {"cat", "dummy"}}, + TID); + } + } + + ~JSONSpan() { + // Finally, record the event (ending at EndTime, not timestamp())! + Tracer->jsonEvent("X", + json::obj{{"name", std::move(Name)}, + {"args", std::move(*Args)}, + {"dur", EndTime - StartTime}}, + TID, StartTime); + } + + // May be called by any thread. + void markEnded() { + EndTime = Tracer->timestamp(); + } + + private: + static uint64_t nextID() { + static std::atomic Next = {0}; + return Next++; + } + + double StartTime; + std::atomic EndTime; // Filled in by markEnded(). + std::string Name; + uint64_t TID; + JSONTracer *Tracer; + json::obj *Args; + }; + static Key> SpanKey; + // Record an event. ph and pid are set. // Contents must be a list of the other JSON key/values. void rawEvent(StringRef Phase, json::obj &&Event) /*REQUIRES(Mu)*/ { @@ -90,6 +154,23 @@ Sep = ",\n"; } + // If we haven't already, emit metadata describing this thread. + void captureThreadMetadata() { + uint64_t TID = get_threadid(); + std::lock_guard Lock(Mu); + if (ThreadsWithMD.insert(TID).second) { + SmallString<32> Name; + get_thread_name(Name); + if (!Name.empty()) { + rawEvent("M", json::obj{ + {"tid", TID}, + {"name", "thread_name"}, + {"args", json::obj{{"name", Name}}}, + }); + } + } + } + double timestamp() { using namespace std::chrono; return duration(system_clock::now() - Start).count(); @@ -103,6 +184,8 @@ const char *JSONFormat; }; +Key> JSONTracer::SpanKey; + EventTracer *T = nullptr; } // namespace @@ -125,20 +208,27 @@ } // Returned context owns Args. -static Context makeSpanContext(llvm::StringRef Name, json::obj *Args) { +static Context makeSpanContext(llvm::Twine Name, json::obj *Args) { if (!T) return Context::current().clone(); WithContextValue WithArgs{std::unique_ptr(Args)}; - return T->beginSpan(Name, Args); + return T->beginSpan(Name.isSingleStringRef() ? Name.getSingleStringRef() + : llvm::StringRef(Name.str()), + Args); } // Span keeps a non-owning pointer to the args, which is how users access them. // The args are owned by the context though. They stick around until the // beginSpan() context is destroyed, when the tracing engine will consume them. -Span::Span(llvm::StringRef Name) +Span::Span(llvm::Twine Name) : Args(T ? new json::obj() : nullptr), RestoreCtx(makeSpanContext(Name, Args)) {} +Span::~Span() { + if (T) + T->endSpan(); +} + } // namespace trace } // namespace clangd } // namespace clang Index: clangd/XRefs.h =================================================================== --- clangd/XRefs.h +++ clangd/XRefs.h @@ -27,6 +27,9 @@ std::vector findDocumentHighlights(ParsedAST &AST, Position Pos); +/// Get the hover information when hovering at \p Pos. +Hover getHover(ParsedAST &AST, Position Pos); + } // namespace clangd } // namespace clang #endif Index: clangd/XRefs.cpp =================================================================== --- clangd/XRefs.cpp +++ clangd/XRefs.cpp @@ -9,6 +9,7 @@ #include "XRefs.h" #include "Logger.h" #include "URI.h" +#include "clang/AST/DeclTemplate.h" #include "clang/Index/IndexDataConsumer.h" #include "clang/Index/IndexingAction.h" #include "llvm/Support/Path.h" @@ -20,7 +21,7 @@ // Get the definition from a given declaration `D`. // Return nullptr if no definition is found, or the declaration type of `D` is // not supported. -const Decl* GetDefinition(const Decl* D) { +const Decl *GetDefinition(const Decl *D) { assert(D); if (const auto *TD = dyn_cast(D)) return TD->getDefinition(); @@ -31,10 +32,15 @@ return nullptr; } +struct MacroDecl { + StringRef Name; + const MacroInfo *Info; +}; + /// Finds declarations locations that a given source location refers to. class DeclarationAndMacrosFinder : public index::IndexDataConsumer { std::vector Decls; - std::vector MacroInfos; + std::vector MacroInfos; const SourceLocation &SearchedLocation; const ASTContext &AST; Preprocessor &PP; @@ -54,10 +60,17 @@ return std::move(Decls); } - std::vector takeMacroInfos() { + std::vector takeMacroInfos() { // Don't keep the same Macro info multiple times. - std::sort(MacroInfos.begin(), MacroInfos.end()); - auto Last = std::unique(MacroInfos.begin(), MacroInfos.end()); + std::sort(MacroInfos.begin(), MacroInfos.end(), + [](const MacroDecl &Left, const MacroDecl &Right) { + return Left.Info < Right.Info; + }); + + auto Last = std::unique(MacroInfos.begin(), MacroInfos.end(), + [](const MacroDecl &Left, const MacroDecl &Right) { + return Left.Info == Right.Info; + }); MacroInfos.erase(Last, MacroInfos.end()); return std::move(MacroInfos); } @@ -72,7 +85,7 @@ // We don't use parameter `D`, as Parameter `D` is the canonical // declaration, which is the first declaration of a redeclarable // declaration, and it could be a forward declaration. - if (const auto* Def = GetDefinition(D)) { + if (const auto *Def = GetDefinition(D)) { Decls.push_back(Def); } else { // Couldn't find a definition, fall back to use `D`. @@ -111,7 +124,7 @@ PP.getMacroDefinitionAtLoc(IdentifierInfo, BeforeSearchedLocation); MacroInfo *MacroInf = MacroDef.getMacroInfo(); if (MacroInf) { - MacroInfos.push_back(MacroInf); + MacroInfos.push_back(MacroDecl{IdentifierInfo->getName(), MacroInf}); } } } @@ -149,7 +162,7 @@ } } - L.uri.file = FilePath.str(); + L.uri = URIForFile(FilePath.str()); L.range = R; return L; } @@ -176,8 +189,7 @@ DeclMacrosFinder, IndexOpts); std::vector Decls = DeclMacrosFinder->takeDecls(); - std::vector MacroInfos = - DeclMacrosFinder->takeMacroInfos(); + std::vector MacroInfos = DeclMacrosFinder->takeMacroInfos(); std::vector Result; for (auto Item : Decls) { @@ -187,7 +199,8 @@ } for (auto Item : MacroInfos) { - SourceRange SR(Item->getDefinitionLoc(), Item->getDefinitionEndLoc()); + SourceRange SR(Item.Info->getDefinitionLoc(), + Item.Info->getDefinitionEndLoc()); auto L = getDeclarationLocation(AST, SR); if (L) Result.push_back(*L); @@ -299,5 +312,137 @@ return DocHighlightsFinder->takeHighlights(); } +static PrintingPolicy PrintingPolicyForDecls(PrintingPolicy Base) { + PrintingPolicy Policy(Base); + + Policy.AnonymousTagLocations = false; + Policy.TerseOutput = true; + Policy.PolishForDeclaration = true; + Policy.ConstantsAsWritten = true; + Policy.SuppressTagKeyword = false; + + return Policy; +} + +/// Return a string representation (e.g. "class MyNamespace::MyClass") of +/// the type declaration \p TD. +static std::string TypeDeclToString(const TypeDecl *TD) { + QualType Type = TD->getASTContext().getTypeDeclType(TD); + + PrintingPolicy Policy = + PrintingPolicyForDecls(TD->getASTContext().getPrintingPolicy()); + + std::string Name; + llvm::raw_string_ostream Stream(Name); + Type.print(Stream, Policy); + + return Stream.str(); +} + +/// Return a string representation (e.g. "namespace ns1::ns2") of +/// the named declaration \p ND. +static std::string NamedDeclQualifiedName(const NamedDecl *ND, + StringRef Prefix) { + PrintingPolicy Policy = + PrintingPolicyForDecls(ND->getASTContext().getPrintingPolicy()); + + std::string Name; + llvm::raw_string_ostream Stream(Name); + Stream << Prefix << ' '; + ND->printQualifiedName(Stream, Policy); + + return Stream.str(); +} + +/// Given a declaration \p D, return a human-readable string representing the +/// scope in which it is declared. If the declaration is in the global scope, +/// return the string "global namespace". +static llvm::Optional getScopeName(const Decl *D) { + const DeclContext *DC = D->getDeclContext(); + + if (isa(DC)) + return std::string("global namespace"); + if (const TypeDecl *TD = dyn_cast(DC)) + return TypeDeclToString(TD); + else if (const NamespaceDecl *ND = dyn_cast(DC)) + return NamedDeclQualifiedName(ND, "namespace"); + else if (const FunctionDecl *FD = dyn_cast(DC)) + return NamedDeclQualifiedName(FD, "function"); + + return llvm::None; +} + +/// Generate a \p Hover object given the declaration \p D. +static Hover getHoverContents(const Decl *D) { + Hover H; + llvm::Optional NamedScope = getScopeName(D); + + // Generate the "Declared in" section. + if (NamedScope) { + assert(!NamedScope->empty()); + + H.contents.value += "Declared in "; + H.contents.value += *NamedScope; + H.contents.value += "\n\n"; + } + + // We want to include the template in the Hover. + if (TemplateDecl *TD = D->getDescribedTemplate()) + D = TD; + + std::string DeclText; + llvm::raw_string_ostream OS(DeclText); + + PrintingPolicy Policy = + PrintingPolicyForDecls(D->getASTContext().getPrintingPolicy()); + + D->print(OS, Policy); + + OS.flush(); + + H.contents.value += DeclText; + return H; +} + +/// Generate a \p Hover object given the macro \p MacroInf. +static Hover getHoverContents(StringRef MacroName) { + Hover H; + + H.contents.value = "#define "; + H.contents.value += MacroName; + + return H; +} + +Hover getHover(ParsedAST &AST, Position Pos) { + const SourceManager &SourceMgr = AST.getASTContext().getSourceManager(); + const FileEntry *FE = SourceMgr.getFileEntryForID(SourceMgr.getMainFileID()); + if (FE == nullptr) + return Hover(); + + SourceLocation SourceLocationBeg = getBeginningOfIdentifier(AST, Pos, FE); + auto DeclMacrosFinder = std::make_shared( + llvm::errs(), SourceLocationBeg, AST.getASTContext(), + AST.getPreprocessor()); + + index::IndexingOptions IndexOpts; + IndexOpts.SystemSymbolFilter = + index::IndexingOptions::SystemSymbolFilterKind::All; + IndexOpts.IndexFunctionLocals = true; + + indexTopLevelDecls(AST.getASTContext(), AST.getTopLevelDecls(), + DeclMacrosFinder, IndexOpts); + + std::vector Macros = DeclMacrosFinder->takeMacroInfos(); + if (!Macros.empty()) + return getHoverContents(Macros[0].Name); + + std::vector Decls = DeclMacrosFinder->takeDecls(); + if (!Decls.empty()) + return getHoverContents(Decls[0]); + + return Hover(); +} + } // namespace clangd } // namespace clang Index: clangd/clients/clangd-vscode/README.md =================================================================== --- clangd/clients/clangd-vscode/README.md +++ clangd/clients/clangd-vscode/README.md @@ -49,3 +49,28 @@ $ code . # When VS Code starts, press . ``` + +## Publish to VS Code Marketplace + +New changes to `clangd-vscode` are not released until a new version is published +to the marketplace. + +### Requirements + +* Make sure install the `vsce` command (`npm install -g vsce`) +* `llvm-vs-code-extensions` account +* Bump the version in `package.json`, and commit the change to upstream + +The extension is published under `llvm-vs-code-extensions` account, which is +currently maintained by clangd developers. If you want to make a new release, +please contact cfe-dev@lists.llvm.org. + +### Steps + +```bash + $ cd /path/to/clang-tools-extra/clangd/clients/clangd-vscode/ + # For the first time, you need to login in the account. vsce will ask you for + the Personal Access Token, and remember it for future commands. + $ vsce login llvm-vs-code-extensions + $ vsce publish +``` Index: clangd/clients/clangd-vscode/package.json =================================================================== --- clangd/clients/clangd-vscode/package.json +++ clangd/clients/clangd-vscode/package.json @@ -2,7 +2,7 @@ "name": "vscode-clangd", "displayName": "vscode-clangd", "description": "Clang Language Server", - "version": "0.0.2", + "version": "0.0.3", "publisher": "llvm-vs-code-extensions", "homepage": "https://clang.llvm.org/extra/clangd.html", "engines": { @@ -43,8 +43,8 @@ "@types/mocha": "^2.2.32" }, "repository": { - "type": "svn", - "url": "http://llvm.org/svn/llvm-project/clang-tools-extra/trunk/clangd/clients/clangd-vscode/" + "type": "svn", + "url": "http://llvm.org/svn/llvm-project/clang-tools-extra/trunk/clangd/clients/clangd-vscode/" }, "contributes": { "configuration": { @@ -68,6 +68,10 @@ "type": "boolean", "default": true, "description": "Whether or not to send file events to clangd (File created, changed or deleted). This can be disabled for performance consideration." + }, + "clangd.trace": { + "type": "string", + "description": "Names a file that clangd should log a performance trace to, in chrome trace-viewer JSON format." } } } Index: clangd/clients/clangd-vscode/src/extension.ts =================================================================== --- clangd/clients/clangd-vscode/src/extension.ts +++ clangd/clients/clangd-vscode/src/extension.ts @@ -16,11 +16,16 @@ * your extension is activated the very first time the command is executed */ export function activate(context: vscode.ExtensionContext) { - const clangdPath = getConfig('path'); - const clangdArgs = getConfig('arguments'); const syncFileEvents = getConfig('syncFileEvents', true); - const serverOptions: vscodelc.ServerOptions = { command: clangdPath, args: clangdArgs }; + const clangd: vscodelc.Executable = { + command: getConfig('path'), + args: getConfig('arguments') + }; + const traceFile = getConfig('trace'); + if (traceFile != null) + clangd.options = {env: {CLANGD_TRACE: traceFile}}; + const serverOptions: vscodelc.ServerOptions = clangd; const filePattern: string = '**/*.{' + ['cpp', 'c', 'cc', 'cxx', 'c++', 'm', 'mm', 'h', 'hh', 'hpp', 'hxx', 'inc'].join() + '}'; Index: clangd/global-symbol-builder/CMakeLists.txt =================================================================== --- clangd/global-symbol-builder/CMakeLists.txt +++ clangd/global-symbol-builder/CMakeLists.txt @@ -15,5 +15,6 @@ clangDaemon clangBasic clangFrontend + clangLex clangTooling ) Index: clangd/global-symbol-builder/GlobalSymbolBuilderMain.cpp =================================================================== --- clangd/global-symbol-builder/GlobalSymbolBuilderMain.cpp +++ clangd/global-symbol-builder/GlobalSymbolBuilderMain.cpp @@ -13,12 +13,13 @@ // //===---------------------------------------------------------------------===// +#include "index/CanonicalIncludes.h" #include "index/Index.h" #include "index/Merge.h" #include "index/SymbolCollector.h" #include "index/SymbolYAML.h" -#include "clang/Frontend/FrontendActions.h" #include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendActions.h" #include "clang/Index/IndexDataConsumer.h" #include "clang/Index/IndexingAction.h" #include "clang/Tooling/CommonOptionsParser.h" @@ -29,6 +30,7 @@ #include "llvm/Support/Path.h" #include "llvm/Support/Signals.h" #include "llvm/Support/ThreadPool.h" +#include "llvm/Support/YAMLTraits.h" using namespace llvm; using namespace clang::tooling; @@ -57,11 +59,19 @@ class WrappedIndexAction : public WrapperFrontendAction { public: WrappedIndexAction(std::shared_ptr C, + std::unique_ptr Includes, const index::IndexingOptions &Opts, tooling::ExecutionContext *Ctx) : WrapperFrontendAction( index::createIndexingAction(C, Opts, nullptr)), - Ctx(Ctx), Collector(C) {} + Ctx(Ctx), Collector(C), Includes(std::move(Includes)), + PragmaHandler(collectIWYUHeaderMaps(this->Includes.get())) {} + + std::unique_ptr + CreateASTConsumer(CompilerInstance &CI, StringRef InFile) override { + CI.getPreprocessor().addCommentHandler(PragmaHandler.get()); + return WrapperFrontendAction::CreateASTConsumer(CI, InFile); + } void EndSourceFileAction() override { WrapperFrontendAction::EndSourceFileAction(); @@ -78,6 +88,8 @@ private: tooling::ExecutionContext *Ctx; std::shared_ptr Collector; + std::unique_ptr Includes; + std::unique_ptr PragmaHandler; }; index::IndexingOptions IndexOpts; @@ -86,9 +98,13 @@ IndexOpts.IndexFunctionLocals = false; auto CollectorOpts = SymbolCollector::Options(); CollectorOpts.FallbackDir = AssumedHeaderDir; + CollectorOpts.CollectIncludePath = true; + auto Includes = llvm::make_unique(); + addSystemHeadersMapping(Includes.get()); + CollectorOpts.Includes = Includes.get(); return new WrappedIndexAction( - std::make_shared(std::move(CollectorOpts)), IndexOpts, - Ctx); + std::make_shared(std::move(CollectorOpts)), + std::move(Includes), IndexOpts, Ctx); } tooling::ExecutionContext *Ctx; @@ -101,7 +117,8 @@ Symbol::Details Scratch; Results->forEachResult([&](llvm::StringRef Key, llvm::StringRef Value) { Arena.Reset(); - auto Sym = clang::clangd::SymbolFromYAML(Value, Arena); + llvm::yaml::Input Yin(Value, &Arena); + auto Sym = clang::clangd::SymbolFromYAML(Yin, Arena); clang::clangd::SymbolID ID; Key >> ID; if (const auto *Existing = UniqueSymbols.find(ID)) Index: clangd/index/CanonicalIncludes.h =================================================================== --- /dev/null +++ clangd/index/CanonicalIncludes.h @@ -0,0 +1,83 @@ +//===-- CanonicalIncludes.h - remap #include header -------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// At indexing time, we decide which file to #included for a symbol. +// Usually this is the file with the canonical decl, but there are exceptions: +// - private headers may have pragmas pointing to the matching public header. +// (These are "IWYU" pragmas, named after the include-what-you-use tool). +// - the standard library is implemented in many files, without any pragmas. +// We have a lookup table for common standard library implementations. +// libstdc++ puts char_traits in bits/char_traits.h, but we #include . +// +//===---------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_CANONICALINCLUDES_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_CANONICALINCLUDES_H + +#include "clang/Lex/Preprocessor.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/Support/Regex.h" +#include +#include +#include + +namespace clang { +namespace clangd { + +/// Maps a definition location onto an #include file, based on a set of filename +/// rules. +/// Only const methods (i.e. mapHeader) in this class are thread safe. +class CanonicalIncludes { +public: + CanonicalIncludes() = default; + + /// Adds a string-to-string mapping from \p Path to \p CanonicalPath. + void addMapping(llvm::StringRef Path, llvm::StringRef CanonicalPath); + + /// Maps all files matching \p RE to \p CanonicalPath + void addRegexMapping(llvm::StringRef RE, llvm::StringRef CanonicalPath); + + /// \return \p Header itself if there is no mapping for it; otherwise, return + /// a canonical header name. + llvm::StringRef mapHeader(llvm::StringRef Header) const; + +private: + // A map from header patterns to header names. This needs to be mutable so + // that we can match again a Regex in a const function member. + // FIXME(ioeric): All the regexes we have so far are suffix matches. The + // performance could be improved by allowing only suffix matches instead of + // arbitrary regexes. + mutable std::vector> + RegexHeaderMappingTable; + // Guards Regex matching as it's not thread-safe. + mutable std::mutex RegexMutex; +}; + +/// Returns a CommentHandler that parses pragma comment on include files to +/// determine when we should include a different header from the header that +/// directly defines a symbol. Mappinps are registered with \p Includes. +/// +/// Currently it only supports IWYU private pragma: +/// https://github.com/include-what-you-use/include-what-you-use/blob/master/docs/IWYUPragmas.md#iwyu-pragma-private +std::unique_ptr +collectIWYUHeaderMaps(CanonicalIncludes *Includes); + +/// Adds mapping for system headers. Approximately, the following system headers +/// are handled: +/// - C++ standard library e.g. bits/basic_string.h$ -> +/// - Posix library e.g. bits/pthreadtypes.h$ -> +/// - Compiler extensions, e.g. include/avx512bwintrin.h$ -> +/// The mapping is hardcoded and hand-maintained, so it might not cover all +/// headers. +void addSystemHeadersMapping(CanonicalIncludes *Includes); + +} // namespace clangd +} // namespace clang + +#endif //LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_HEADERMAPCOLLECTOR_H Index: clangd/index/CanonicalIncludes.cpp =================================================================== --- /dev/null +++ clangd/index/CanonicalIncludes.cpp @@ -0,0 +1,706 @@ +//===-- CanonicalIncludes.h - remap #inclue headers--------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "CanonicalIncludes.h" +#include "llvm/Support/Regex.h" + +namespace clang { +namespace clangd { +namespace { +const char IWYUPragma[] = "// IWYU pragma: private, include "; +} // namespace + +void CanonicalIncludes::addMapping(llvm::StringRef Path, + llvm::StringRef CanonicalPath) { + addRegexMapping((llvm::Twine("^") + llvm::Regex::escape(Path) + "$").str(), + CanonicalPath); +} + +void CanonicalIncludes::addRegexMapping(llvm::StringRef RE, + llvm::StringRef CanonicalPath) { + this->RegexHeaderMappingTable.emplace_back(llvm::Regex(RE), CanonicalPath); +} + +llvm::StringRef CanonicalIncludes::mapHeader(llvm::StringRef Header) const { + std::lock_guard Lock(RegexMutex); + for (auto &Entry : RegexHeaderMappingTable) { +#ifndef NDEBUG + std::string Dummy; + assert(Entry.first.isValid(Dummy) && "Regex should never be invalid!"); +#endif + if (Entry.first.match(Header)) + return Entry.second; + } + return Header; +} + +std::unique_ptr +collectIWYUHeaderMaps(CanonicalIncludes *Includes) { + class PragmaCommentHandler : public clang::CommentHandler { + public: + PragmaCommentHandler(CanonicalIncludes *Includes) : Includes(Includes) {} + + bool HandleComment(Preprocessor &PP, SourceRange Range) override { + StringRef Text = + Lexer::getSourceText(CharSourceRange::getCharRange(Range), + PP.getSourceManager(), PP.getLangOpts()); + if (!Text.consume_front(IWYUPragma)) + return false; + // FIXME(ioeric): resolve the header and store actual file path. For now, + // we simply assume the written header is suitable to be #included. + Includes->addMapping(PP.getSourceManager().getFilename(Range.getBegin()), + Text.startswith("<") ? Text.str() + : ("\"" + Text + "\"").str()); + return false; + } + + private: + CanonicalIncludes *const Includes; + }; + return llvm::make_unique(Includes); +} + +void addSystemHeadersMapping(CanonicalIncludes *Includes) { + static const std::vector> + SystemHeaderMap = { + {"include/__stddef_max_align_t.h$", ""}, + {"include/__wmmintrin_aes.h$", ""}, + {"include/__wmmintrin_pclmul.h$", ""}, + {"include/adxintrin.h$", ""}, + {"include/ammintrin.h$", ""}, + {"include/avx2intrin.h$", ""}, + {"include/avx512bwintrin.h$", ""}, + {"include/avx512cdintrin.h$", ""}, + {"include/avx512dqintrin.h$", ""}, + {"include/avx512erintrin.h$", ""}, + {"include/avx512fintrin.h$", ""}, + {"include/avx512ifmaintrin.h$", ""}, + {"include/avx512ifmavlintrin.h$", ""}, + {"include/avx512pfintrin.h$", ""}, + {"include/avx512vbmiintrin.h$", ""}, + {"include/avx512vbmivlintrin.h$", ""}, + {"include/avx512vlbwintrin.h$", ""}, + {"include/avx512vlcdintrin.h$", ""}, + {"include/avx512vldqintrin.h$", ""}, + {"include/avx512vlintrin.h$", ""}, + {"include/avxintrin.h$", ""}, + {"include/bmi2intrin.h$", ""}, + {"include/bmiintrin.h$", ""}, + {"include/emmintrin.h$", ""}, + {"include/f16cintrin.h$", ""}, + {"include/float.h$", ""}, + {"include/fma4intrin.h$", ""}, + {"include/fmaintrin.h$", ""}, + {"include/fxsrintrin.h$", ""}, + {"include/ia32intrin.h$", ""}, + {"include/immintrin.h$", ""}, + {"include/inttypes.h$", ""}, + {"include/limits.h$", ""}, + {"include/lzcntintrin.h$", ""}, + {"include/mm3dnow.h$", ""}, + {"include/mm_malloc.h$", ""}, + {"include/mmintrin.h$", ""}, + {"include/mwaitxintrin.h$", ""}, + {"include/pkuintrin.h$", ""}, + {"include/pmmintrin.h$", ""}, + {"include/popcntintrin.h$", ""}, + {"include/prfchwintrin.h$", ""}, + {"include/rdseedintrin.h$", ""}, + {"include/rtmintrin.h$", ""}, + {"include/shaintrin.h$", ""}, + {"include/smmintrin.h$", ""}, + {"include/stdalign.h$", ""}, + {"include/stdarg.h$", ""}, + {"include/stdbool.h$", ""}, + {"include/stddef.h$", ""}, + {"include/stdint.h$", ""}, + {"include/tbmintrin.h$", ""}, + {"include/tmmintrin.h$", ""}, + {"include/wmmintrin.h$", ""}, + {"include/x86intrin.h$", ""}, + {"include/xmmintrin.h$", ""}, + {"include/xopintrin.h$", ""}, + {"include/xsavecintrin.h$", ""}, + {"include/xsaveintrin.h$", ""}, + {"include/xsaveoptintrin.h$", ""}, + {"include/xsavesintrin.h$", ""}, + {"include/xtestintrin.h$", ""}, + {"include/_G_config.h$", ""}, + {"include/assert.h$", ""}, + {"algorithm$", ""}, + {"array$", ""}, + {"atomic$", ""}, + {"backward/auto_ptr.h$", ""}, + {"backward/binders.h$", ""}, + {"bits/algorithmfwd.h$", ""}, + {"bits/alloc_traits.h$", ""}, + {"bits/allocator.h$", ""}, + {"bits/atomic_base.h$", ""}, + {"bits/atomic_lockfree_defines.h$", ""}, + {"bits/basic_ios.h$", ""}, + {"bits/basic_ios.tcc$", ""}, + {"bits/basic_string.h$", ""}, + {"bits/basic_string.tcc$", ""}, + {"bits/char_traits.h$", ""}, + {"bits/codecvt.h$", ""}, + {"bits/concept_check.h$", ""}, + {"bits/cpp_type_traits.h$", ""}, + {"bits/cxxabi_forced.h$", ""}, + {"bits/deque.tcc$", ""}, + {"bits/exception_defines.h$", ""}, + {"bits/exception_ptr.h$", ""}, + {"bits/forward_list.h$", ""}, + {"bits/forward_list.tcc$", ""}, + {"bits/fstream.tcc$", ""}, + {"bits/functexcept.h$", ""}, + {"bits/functional_hash.h$", ""}, + {"bits/gslice.h$", ""}, + {"bits/gslice_array.h$", ""}, + {"bits/hash_bytes.h$", ""}, + {"bits/hashtable.h$", ""}, + {"bits/hashtable_policy.h$", ""}, + {"bits/indirect_array.h$", ""}, + {"bits/ios_base.h$", ""}, + {"bits/istream.tcc$", ""}, + {"bits/list.tcc$", ""}, + {"bits/locale_classes.h$", ""}, + {"bits/locale_classes.tcc$", ""}, + {"bits/locale_facets.h$", ""}, + {"bits/locale_facets.tcc$", ""}, + {"bits/locale_facets_nonio.h$", ""}, + {"bits/locale_facets_nonio.tcc$", ""}, + {"bits/localefwd.h$", ""}, + {"bits/mask_array.h$", ""}, + {"bits/memoryfwd.h$", ""}, + {"bits/move.h$", ""}, + {"bits/nested_exception.h$", ""}, + {"bits/ostream.tcc$", ""}, + {"bits/ostream_insert.h$", ""}, + {"bits/postypes.h$", ""}, + {"bits/ptr_traits.h$", ""}, + {"bits/random.h$", ""}, + {"bits/random.tcc$", ""}, + {"bits/range_access.h$", ""}, + {"bits/regex.h$", ""}, + {"bits/regex_compiler.h$", ""}, + {"bits/regex_constants.h$", ""}, + {"bits/regex_cursor.h$", ""}, + {"bits/regex_error.h$", ""}, + {"bits/regex_grep_matcher.h$", ""}, + {"bits/regex_grep_matcher.tcc$", ""}, + {"bits/regex_nfa.h$", ""}, + {"bits/shared_ptr.h$", ""}, + {"bits/shared_ptr_base.h$", ""}, + {"bits/slice_array.h$", ""}, + {"bits/sstream.tcc$", ""}, + {"bits/stl_algo.h$", ""}, + {"bits/stl_algobase.h$", ""}, + {"bits/stl_bvector.h$", ""}, + {"bits/stl_construct.h$", ""}, + {"bits/stl_deque.h$", ""}, + {"bits/stl_function.h$", ""}, + {"bits/stl_heap.h$", ""}, + {"bits/stl_iterator.h$", ""}, + {"bits/stl_iterator_base_funcs.h$", ""}, + {"bits/stl_iterator_base_types.h$", ""}, + {"bits/stl_list.h$", ""}, + {"bits/stl_map.h$", ""}, + {"bits/stl_multimap.h$", ""}, + {"bits/stl_multiset.h$", ""}, + {"bits/stl_numeric.h$", ""}, + {"bits/stl_pair.h$", ""}, + {"bits/stl_queue.h$", ""}, + {"bits/stl_raw_storage_iter.h$", ""}, + {"bits/stl_relops.h$", ""}, + {"bits/stl_set.h$", ""}, + {"bits/stl_stack.h$", ""}, + {"bits/stl_tempbuf.h$", ""}, + {"bits/stl_tree.h$", ""}, + {"bits/stl_uninitialized.h$", ""}, + {"bits/stl_vector.h$", ""}, + {"bits/stream_iterator.h$", ""}, + {"bits/streambuf.tcc$", ""}, + {"bits/streambuf_iterator.h$", ""}, + {"bits/stringfwd.h$", ""}, + {"bits/unique_ptr.h$", ""}, + {"bits/unordered_map.h$", ""}, + {"bits/unordered_set.h$", ""}, + {"bits/uses_allocator.h$", ""}, + {"bits/valarray_after.h$", ""}, + {"bits/valarray_array.h$", ""}, + {"bits/valarray_array.tcc$", ""}, + {"bits/valarray_before.h$", ""}, + {"bits/vector.tcc$", ""}, + {"bitset$", ""}, + {"ccomplex$", ""}, + {"cctype$", ""}, + {"cerrno$", ""}, + {"cfenv$", ""}, + {"cfloat$", ""}, + {"chrono$", ""}, + {"cinttypes$", ""}, + {"climits$", ""}, + {"clocale$", ""}, + {"cmath$", ""}, + {"complex$", ""}, + {"complex.h$", ""}, + {"condition_variable$", ""}, + {"csetjmp$", ""}, + {"csignal$", ""}, + {"cstdalign$", ""}, + {"cstdarg$", ""}, + {"cstdbool$", ""}, + {"cstdint$", ""}, + {"cstdio$", ""}, + {"cstdlib$", ""}, + {"cstring$", ""}, + {"ctgmath$", ""}, + {"ctime$", ""}, + {"cwchar$", ""}, + {"cwctype$", ""}, + {"cxxabi.h$", ""}, + {"debug/debug.h$", ""}, + {"deque$", ""}, + {"exception$", ""}, + {"ext/alloc_traits.h$", ""}, + {"ext/atomicity.h$", ""}, + {"ext/concurrence.h$", ""}, + {"ext/new_allocator.h$", ""}, + {"ext/numeric_traits.h$", ""}, + {"ext/string_conversions.h$", ""}, + {"ext/type_traits.h$", ""}, + {"fenv.h$", ""}, + {"forward_list$", ""}, + {"fstream$", ""}, + {"functional$", ""}, + {"future$", ""}, + {"initializer_list$", ""}, + {"iomanip$", ""}, + {"ios$", ""}, + {"iosfwd$", ""}, + {"iostream$", ""}, + {"istream$", ""}, + {"iterator$", ""}, + {"limits$", ""}, + {"list$", ""}, + {"locale$", ""}, + {"map$", ""}, + {"memory$", ""}, + {"mutex$", ""}, + {"new$", ""}, + {"numeric$", ""}, + {"ostream$", ""}, + {"queue$", ""}, + {"random$", ""}, + {"ratio$", ""}, + {"regex$", ""}, + {"scoped_allocator$", ""}, + {"set$", ""}, + {"sstream$", ""}, + {"stack$", ""}, + {"stdexcept$", ""}, + {"streambuf$", ""}, + {"string$", ""}, + {"system_error$", ""}, + {"tgmath.h$", ""}, + {"thread$", ""}, + {"tuple$", ""}, + {"type_traits$", ""}, + {"typeindex$", ""}, + {"typeinfo$", ""}, + {"unordered_map$", ""}, + {"unordered_set$", ""}, + {"utility$", ""}, + {"valarray$", ""}, + {"vector$", ""}, + {"include/complex.h$", ""}, + {"include/ctype.h$", ""}, + {"include/errno.h$", ""}, + {"include/fenv.h$", ""}, + {"include/inttypes.h$", ""}, + {"include/libio.h$", ""}, + {"include/limits.h$", ""}, + {"include/locale.h$", ""}, + {"include/math.h$", ""}, + {"include/setjmp.h$", ""}, + {"include/signal.h$", ""}, + {"include/stdint.h$", ""}, + {"include/stdio.h$", ""}, + {"include/stdlib.h$", ""}, + {"include/string.h$", ""}, + {"include/time.h$", ""}, + {"include/wchar.h$", ""}, + {"include/wctype.h$", ""}, + {"bits/cmathcalls.h$", ""}, + {"bits/errno.h$", ""}, + {"bits/fenv.h$", ""}, + {"bits/huge_val.h$", ""}, + {"bits/huge_valf.h$", ""}, + {"bits/huge_vall.h$", ""}, + {"bits/inf.h$", ""}, + {"bits/local_lim.h$", ""}, + {"bits/locale.h$", ""}, + {"bits/mathcalls.h$", ""}, + {"bits/mathdef.h$", ""}, + {"bits/nan.h$", ""}, + {"bits/posix1_lim.h$", ""}, + {"bits/posix2_lim.h$", ""}, + {"bits/setjmp.h$", ""}, + {"bits/sigaction.h$", ""}, + {"bits/sigcontext.h$", ""}, + {"bits/siginfo.h$", ""}, + {"bits/signum.h$", ""}, + {"bits/sigset.h$", ""}, + {"bits/sigstack.h$", ""}, + {"bits/stdio_lim.h$", ""}, + {"bits/sys_errlist.h$", ""}, + {"bits/time.h$", ""}, + {"bits/timex.h$", ""}, + {"bits/typesizes.h$", ""}, + {"bits/wchar.h$", ""}, + {"bits/wordsize.h$", ""}, + {"bits/xopen_lim.h$", ""}, + {"include/xlocale.h$", ""}, + {"bits/atomic_word.h$", ""}, + {"bits/basic_file.h$", ""}, + {"bits/c\\+\\+allocator.h$", ""}, + {"bits/c\\+\\+config.h$", ""}, + {"bits/c\\+\\+io.h$", ""}, + {"bits/c\\+\\+locale.h$", ""}, + {"bits/cpu_defines.h$", ""}, + {"bits/ctype_base.h$", ""}, + {"bits/cxxabi_tweaks.h$", ""}, + {"bits/error_constants.h$", ""}, + {"bits/gthr-default.h$", ""}, + {"bits/gthr.h$", ""}, + {"bits/opt_random.h$", ""}, + {"bits/os_defines.h$", ""}, + // GNU C headers + {"include/aio.h$", ""}, + {"include/aliases.h$", ""}, + {"include/alloca.h$", ""}, + {"include/ar.h$", ""}, + {"include/argp.h$", ""}, + {"include/argz.h$", ""}, + {"include/arpa/nameser.h$", ""}, + {"include/arpa/nameser_compat.h$", ""}, + {"include/byteswap.h$", ""}, + {"include/cpio.h$", ""}, + {"include/crypt.h$", ""}, + {"include/dirent.h$", ""}, + {"include/dlfcn.h$", ""}, + {"include/elf.h$", ""}, + {"include/endian.h$", ""}, + {"include/envz.h$", ""}, + {"include/err.h$", ""}, + {"include/error.h$", ""}, + {"include/execinfo.h$", ""}, + {"include/fcntl.h$", ""}, + {"include/features.h$", ""}, + {"include/fenv.h$", ""}, + {"include/fmtmsg.h$", ""}, + {"include/fnmatch.h$", ""}, + {"include/fstab.h$", ""}, + {"include/fts.h$", ""}, + {"include/ftw.h$", ""}, + {"include/gconv.h$", ""}, + {"include/getopt.h$", ""}, + {"include/glob.h$", ""}, + {"include/grp.h$", ""}, + {"include/gshadow.h$", ""}, + {"include/iconv.h$", ""}, + {"include/ifaddrs.h$", ""}, + {"include/kdb.h$", ""}, + {"include/langinfo.h$", ""}, + {"include/libgen.h$", ""}, + {"include/libintl.h$", ""}, + {"include/link.h$", ""}, + {"include/malloc.h$", ""}, + {"include/mcheck.h$", ""}, + {"include/memory.h$", ""}, + {"include/mntent.h$", ""}, + {"include/monetary.h$", ""}, + {"include/mqueue.h$", ""}, + {"include/netdb.h$", ""}, + {"include/netinet/in.h$", ""}, + {"include/nl_types.h$", ""}, + {"include/nss.h$", ""}, + {"include/obstack.h$", ""}, + {"include/panel.h$", ""}, + {"include/paths.h$", ""}, + {"include/printf.h$", ""}, + {"include/profile.h$", ""}, + {"include/pthread.h$", ""}, + {"include/pty.h$", ""}, + {"include/pwd.h$", ""}, + {"include/re_comp.h$", ""}, + {"include/regex.h$", ""}, + {"include/regexp.h$", ""}, + {"include/resolv.h$", ""}, + {"include/rpc/netdb.h$", ""}, + {"include/sched.h$", ""}, + {"include/search.h$", ""}, + {"include/semaphore.h$", ""}, + {"include/sgtty.h$", ""}, + {"include/shadow.h$", ""}, + {"include/spawn.h$", ""}, + {"include/stab.h$", ""}, + {"include/stdc-predef.h$", ""}, + {"include/stdio_ext.h$", ""}, + {"include/strings.h$", ""}, + {"include/stropts.h$", ""}, + {"include/sudo_plugin.h$", ""}, + {"include/sysexits.h$", ""}, + {"include/tar.h$", ""}, + {"include/tcpd.h$", ""}, + {"include/term.h$", ""}, + {"include/term_entry.h$", ""}, + {"include/termcap.h$", ""}, + {"include/termios.h$", ""}, + {"include/thread_db.h$", ""}, + {"include/tic.h$", ""}, + {"include/ttyent.h$", ""}, + {"include/uchar.h$", ""}, + {"include/ucontext.h$", ""}, + {"include/ulimit.h$", ""}, + {"include/unctrl.h$", ""}, + {"include/unistd.h$", ""}, + {"include/utime.h$", ""}, + {"include/utmp.h$", ""}, + {"include/utmpx.h$", ""}, + {"include/values.h$", ""}, + {"include/wordexp.h$", ""}, + {"fpu_control.h$", ""}, + {"ieee754.h$", ""}, + {"include/xlocale.h$", ""}, + {"gnu/lib-names.h$", ""}, + {"gnu/libc-version.h$", ""}, + {"gnu/option-groups.h$", ""}, + {"gnu/stubs-32.h$", ""}, + {"gnu/stubs-64.h$", ""}, + {"gnu/stubs-x32.h$", ""}, + {"include/rpc/auth_des.h$", ""}, + {"include/rpc/rpc_msg.h$", ""}, + {"include/rpc/pmap_clnt.h$", ""}, + {"include/rpc/rpc.h$", ""}, + {"include/rpc/types.h$", ""}, + {"include/rpc/auth_unix.h$", ""}, + {"include/rpc/key_prot.h$", ""}, + {"include/rpc/pmap_prot.h$", ""}, + {"include/rpc/auth.h$", ""}, + {"include/rpc/svc_auth.h$", ""}, + {"include/rpc/xdr.h$", ""}, + {"include/rpc/pmap_rmt.h$", ""}, + {"include/rpc/des_crypt.h$", ""}, + {"include/rpc/svc.h$", ""}, + {"include/rpc/rpc_des.h$", ""}, + {"include/rpc/clnt.h$", ""}, + {"include/scsi/scsi.h$", ""}, + {"include/scsi/sg.h$", ""}, + {"include/scsi/scsi_ioctl.h$", ""}, + {"include/netrose/rose.h$", ""}, + {"include/nfs/nfs.h$", ""}, + {"include/netatalk/at.h$", ""}, + {"include/netinet/ether.h$", ""}, + {"include/netinet/icmp6.h$", ""}, + {"include/netinet/if_ether.h$", ""}, + {"include/netinet/if_fddi.h$", ""}, + {"include/netinet/if_tr.h$", ""}, + {"include/netinet/igmp.h$", ""}, + {"include/netinet/in.h$", ""}, + {"include/netinet/in_systm.h$", ""}, + {"include/netinet/ip.h$", ""}, + {"include/netinet/ip6.h$", ""}, + {"include/netinet/ip_icmp.h$", ""}, + {"include/netinet/tcp.h$", ""}, + {"include/netinet/udp.h$", ""}, + {"include/netrom/netrom.h$", ""}, + {"include/protocols/routed.h$", ""}, + {"include/protocols/rwhod.h$", ""}, + {"include/protocols/talkd.h$", ""}, + {"include/protocols/timed.h$", ""}, + {"include/rpcsvc/klm_prot.x$", ""}, + {"include/rpcsvc/rstat.h$", ""}, + {"include/rpcsvc/spray.x$", ""}, + {"include/rpcsvc/nlm_prot.x$", ""}, + {"include/rpcsvc/nis_callback.x$", ""}, + {"include/rpcsvc/yp.h$", ""}, + {"include/rpcsvc/yp.x$", ""}, + {"include/rpcsvc/nfs_prot.h$", ""}, + {"include/rpcsvc/rex.h$", ""}, + {"include/rpcsvc/yppasswd.h$", ""}, + {"include/rpcsvc/rex.x$", ""}, + {"include/rpcsvc/nis_tags.h$", ""}, + {"include/rpcsvc/nis_callback.h$", ""}, + {"include/rpcsvc/nfs_prot.x$", ""}, + {"include/rpcsvc/bootparam_prot.x$", ""}, + {"include/rpcsvc/rusers.x$", ""}, + {"include/rpcsvc/rquota.x$", ""}, + {"include/rpcsvc/nis.h$", ""}, + {"include/rpcsvc/nislib.h$", ""}, + {"include/rpcsvc/ypupd.h$", ""}, + {"include/rpcsvc/bootparam.h$", ""}, + {"include/rpcsvc/spray.h$", ""}, + {"include/rpcsvc/key_prot.h$", ""}, + {"include/rpcsvc/klm_prot.h$", ""}, + {"include/rpcsvc/sm_inter.h$", ""}, + {"include/rpcsvc/nlm_prot.h$", ""}, + {"include/rpcsvc/yp_prot.h$", ""}, + {"include/rpcsvc/ypclnt.h$", ""}, + {"include/rpcsvc/rstat.x$", ""}, + {"include/rpcsvc/rusers.h$", ""}, + {"include/rpcsvc/key_prot.x$", ""}, + {"include/rpcsvc/sm_inter.x$", ""}, + {"include/rpcsvc/rquota.h$", ""}, + {"include/rpcsvc/nis.x$", ""}, + {"include/rpcsvc/bootparam_prot.h$", ""}, + {"include/rpcsvc/mount.h$", ""}, + {"include/rpcsvc/mount.x$", ""}, + {"include/rpcsvc/nis_object.x$", ""}, + {"include/rpcsvc/yppasswd.x$", ""}, + {"sys/acct.h$", ""}, + {"sys/auxv.h$", ""}, + {"sys/cdefs.h$", ""}, + {"sys/debugreg.h$", ""}, + {"sys/dir.h$", ""}, + {"sys/elf.h$", ""}, + {"sys/epoll.h$", ""}, + {"sys/eventfd.h$", ""}, + {"sys/fanotify.h$", ""}, + {"sys/file.h$", ""}, + {"sys/fsuid.h$", ""}, + {"sys/gmon.h$", ""}, + {"sys/gmon_out.h$", ""}, + {"sys/inotify.h$", ""}, + {"sys/io.h$", ""}, + {"sys/ioctl.h$", ""}, + {"sys/ipc.h$", ""}, + {"sys/kd.h$", ""}, + {"sys/kdaemon.h$", ""}, + {"sys/klog.h$", ""}, + {"sys/mman.h$", ""}, + {"sys/mount.h$", ""}, + {"sys/msg.h$", ""}, + {"sys/mtio.h$", ""}, + {"sys/param.h$", ""}, + {"sys/pci.h$", ""}, + {"sys/perm.h$", ""}, + {"sys/personality.h$", ""}, + {"sys/poll.h$", ""}, + {"sys/prctl.h$", ""}, + {"sys/procfs.h$", ""}, + {"sys/profil.h$", ""}, + {"sys/ptrace.h$", ""}, + {"sys/queue.h$", ""}, + {"sys/quota.h$", ""}, + {"sys/raw.h$", ""}, + {"sys/reboot.h$", ""}, + {"sys/reg.h$", ""}, + {"sys/resource.h$", ""}, + {"sys/select.h$", ""}, + {"sys/sem.h$", ""}, + {"sys/sendfile.h$", ""}, + {"sys/shm.h$", ""}, + {"sys/signalfd.h$", ""}, + {"sys/socket.h$", ""}, + {"sys/stat.h$", ""}, + {"sys/statfs.h$", ""}, + {"sys/statvfs.h$", ""}, + {"sys/swap.h$", ""}, + {"sys/syscall.h$", ""}, + {"sys/sysctl.h$", ""}, + {"sys/sysinfo.h$", ""}, + {"sys/syslog.h$", ""}, + {"sys/sysmacros.h$", ""}, + {"sys/termios.h$", ""}, + {"sys/time.h$", ""}, + {"sys/timeb.h$", ""}, + {"sys/timerfd.h$", ""}, + {"sys/times.h$", ""}, + {"sys/timex.h$", ""}, + {"sys/ttychars.h$", ""}, + {"sys/ttydefaults.h$", ""}, + {"sys/types.h$", ""}, + {"sys/ucontext.h$", ""}, + {"sys/uio.h$", ""}, + {"sys/un.h$", ""}, + {"sys/user.h$", ""}, + {"sys/ustat.h$", ""}, + {"sys/utsname.h$", ""}, + {"sys/vlimit.h$", ""}, + {"sys/vm86.h$", ""}, + {"sys/vtimes.h$", ""}, + {"sys/wait.h$", ""}, + {"sys/xattr.h$", ""}, + {"bits/epoll.h$", ""}, + {"bits/eventfd.h$", ""}, + {"bits/inotify.h$", ""}, + {"bits/ipc.h$", ""}, + {"bits/ipctypes.h$", ""}, + {"bits/mman-linux.h$", ""}, + {"bits/mman.h$", ""}, + {"bits/msq.h$", ""}, + {"bits/resource.h$", ""}, + {"bits/sem.h$", ""}, + {"bits/shm.h$", ""}, + {"bits/signalfd.h$", ""}, + {"bits/statfs.h$", ""}, + {"bits/statvfs.h$", ""}, + {"bits/timerfd.h$", ""}, + {"bits/utsname.h$", ""}, + {"bits/auxv.h$", ""}, + {"bits/byteswap-16.h$", ""}, + {"bits/byteswap.h$", ""}, + {"bits/confname.h$", ""}, + {"bits/dirent.h$", ""}, + {"bits/dlfcn.h$", ""}, + {"bits/elfclass.h$", ""}, + {"bits/endian.h$", ""}, + {"bits/environments.h$", ""}, + {"bits/fcntl-linux.h$", ""}, + {"bits/fcntl.h$", ""}, + {"bits/in.h$", ""}, + {"bits/ioctl-types.h$", ""}, + {"bits/ioctls.h$", ""}, + {"bits/link.h$", ""}, + {"bits/mqueue.h$", ""}, + {"bits/netdb.h$", ""}, + {"bits/param.h$", ""}, + {"bits/poll.h$", ""}, + {"bits/posix_opt.h$", ""}, + {"bits/pthreadtypes.h$", ""}, + {"bits/sched.h$", ""}, + {"bits/select.h$", ""}, + {"bits/semaphore.h$", ""}, + {"bits/sigthread.h$", ""}, + {"bits/sockaddr.h$", ""}, + {"bits/socket.h$", ""}, + {"bits/socket_type.h$", ""}, + {"bits/stab.def$", ""}, + {"bits/stat.h$", ""}, + {"bits/stropts.h$", ""}, + {"bits/syscall.h$", ""}, + {"bits/syslog-path.h$", ""}, + {"bits/termios.h$", ""}, + {"bits/types.h$", ""}, + {"bits/typesizes.h$", ""}, + {"bits/uio.h$", ""}, + {"bits/ustat.h$", ""}, + {"bits/utmp.h$", ""}, + {"bits/utmpx.h$", ""}, + {"bits/waitflags.h$", ""}, + {"bits/waitstatus.h$", ""}, + {"bits/xtitypes.h$", ""}, + }; + for (const auto &Pair : SystemHeaderMap) + Includes->addRegexMapping(Pair.first, Pair.second); +} + +} // namespace clangd +} // namespace clang Index: clangd/index/FileIndex.cpp =================================================================== --- clangd/index/FileIndex.cpp +++ clangd/index/FileIndex.cpp @@ -15,14 +15,30 @@ namespace clangd { namespace { +const CanonicalIncludes *canonicalIncludesForSystemHeaders() { + static const auto *Includes = [] { + auto *I = new CanonicalIncludes(); + addSystemHeadersMapping(I); + return I; + }(); + return Includes; +} + /// Retrieves namespace and class level symbols in \p Decls. std::unique_ptr indexAST(ASTContext &Ctx, std::shared_ptr PP, llvm::ArrayRef Decls) { SymbolCollector::Options CollectorOpts; - // Code completion gets main-file results from Sema. - // But we leave this option on because features like go-to-definition want it. - CollectorOpts.IndexMainFiles = true; + // Although we do not index symbols in main files (e.g. cpp file), information + // in main files like definition locations of class declarations will still be + // collected; thus, the index works for go-to-definition. + // FIXME(ioeric): handle IWYU pragma for dynamic index. We might want to make + // SymbolCollector always provides include canonicalization (e.g. IWYU, STL). + // FIXME(ioeric): get rid of `IndexMainFiles` as this is always set to false. + CollectorOpts.IndexMainFiles = false; + CollectorOpts.CollectIncludePath = true; + CollectorOpts.Includes = canonicalIncludesForSystemHeaders(); + auto Collector = std::make_shared(std::move(CollectorOpts)); Collector->setPreprocessor(std::move(PP)); index::IndexingOptions IndexOpts; Index: clangd/index/Index.h =================================================================== --- clangd/index/Index.h +++ clangd/index/Index.h @@ -152,11 +152,19 @@ /// and have clients resolve full symbol information for a specific candidate /// if needed. struct Details { - // Documentation including comment for the symbol declaration. + /// Documentation including comment for the symbol declaration. llvm::StringRef Documentation; - // This is what goes into the LSP detail field in a completion item. For - // example, the result type of a function. + /// This is what goes into the LSP detail field in a completion item. For + /// example, the result type of a function. llvm::StringRef CompletionDetail; + /// This can be either a URI of the header to be #include'd for this symbol, + /// or a literal header quoted with <> or "" that is suitable to be included + /// directly. When this is a URI, the exact #include path needs to be + /// calculated according to the URI scheme. + /// + /// If empty, FileURI in CanonicalDeclaration should be used to calculate + /// the #include path. + llvm::StringRef IncludeHeader; }; // Optional details of the symbol. @@ -247,8 +255,7 @@ /// each matched symbol before returning. /// If returned Symbols are used outside Callback, they must be deep-copied! /// - /// Returns true if the result list is complete, false if it was truncated due - /// to MaxCandidateCount + /// Returns true if there may be more results (limited by MaxCandidateCount). virtual bool fuzzyFind(const FuzzyFindRequest &Req, llvm::function_ref Callback) const = 0; Index: clangd/index/Index.cpp =================================================================== --- clangd/index/Index.cpp +++ clangd/index/Index.cpp @@ -80,6 +80,7 @@ // Intern the actual strings. Intern(Detail->Documentation); Intern(Detail->CompletionDetail); + Intern(Detail->IncludeHeader); // Replace the detail pointer with our copy. S.Detail = Detail; } Index: clangd/index/Merge.cpp =================================================================== --- clangd/index/Merge.cpp +++ clangd/index/Merge.cpp @@ -49,7 +49,7 @@ for (const Symbol &S : Dyn) if (!SeenDynamicSymbols.count(S.ID)) Callback(S); - return !More; // returning true indicates the result is complete. + return More; } private: @@ -90,6 +90,8 @@ Scratch->Documentation = O.Detail->Documentation; if (Scratch->CompletionDetail == "") Scratch->CompletionDetail = O.Detail->CompletionDetail; + if (Scratch->IncludeHeader == "") + Scratch->IncludeHeader = O.Detail->IncludeHeader; S.Detail = Scratch; } else S.Detail = O.Detail; Index: clangd/index/SymbolCollector.h =================================================================== --- clangd/index/SymbolCollector.h +++ clangd/index/SymbolCollector.h @@ -7,6 +7,7 @@ // //===----------------------------------------------------------------------===// +#include "CanonicalIncludes.h" #include "Index.h" #include "clang/AST/ASTContext.h" #include "clang/AST/Decl.h" @@ -39,6 +40,10 @@ /// in symbols. The list of schemes will be tried in order until a working /// scheme is found. If no scheme works, symbol location will be dropped. std::vector URISchemes = {"file"}; + bool CollectIncludePath = false; + /// If set, this is used to map symbol #include path to a potentially + /// different #include path. + const CanonicalIncludes *Includes = nullptr; }; SymbolCollector(Options Opts); Index: clangd/index/SymbolCollector.cpp =================================================================== --- clangd/index/SymbolCollector.cpp +++ clangd/index/SymbolCollector.cpp @@ -11,6 +11,7 @@ #include "../CodeCompletionStrings.h" #include "../Logger.h" #include "../URI.h" +#include "CanonicalIncludes.h" #include "clang/AST/DeclCXX.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/Basic/SourceManager.h" @@ -127,6 +128,51 @@ return false; } +// We only collect #include paths for symbols that are suitable for global code +// completion, except for namespaces since #include path for a namespace is hard +// to define. +bool shouldCollectIncludePath(index::SymbolKind Kind) { + using SK = index::SymbolKind; + switch (Kind) { + case SK::Macro: + case SK::Enum: + case SK::Struct: + case SK::Class: + case SK::Union: + case SK::TypeAlias: + case SK::Using: + case SK::Function: + case SK::Variable: + case SK::EnumConstant: + return true; + default: + return false; + } +} + +/// Gets a canonical include (
or "header") for header of \p Loc. +/// Returns None if the header has no canonical include. +/// FIXME: we should handle .inc files whose symbols are expected be exported by +/// their containing headers. +llvm::Optional +getIncludeHeader(const SourceManager &SM, SourceLocation Loc, + const SymbolCollector::Options &Opts) { + llvm::StringRef FilePath = SM.getFilename(Loc); + if (FilePath.empty()) + return llvm::None; + if (Opts.Includes) { + llvm::StringRef Mapped = Opts.Includes->mapHeader(FilePath); + if (Mapped != FilePath) + return (Mapped.startswith("<") || Mapped.startswith("\"")) + ? Mapped.str() + : ("\"" + Mapped + "\"").str(); + } + // If the header path is the same as the file path of the declaration, we skip + // storing the #include path; users can use the URI in declaration location to + // calculate the #include path. + return llvm::None; +} + // Return the symbol location of the given declaration `D`. // // For symbols defined inside macros: @@ -252,6 +298,14 @@ std::string Documentation = getDocumentation(*CCS); std::string CompletionDetail = getDetail(*CCS); + std::string Include; + if (Opts.CollectIncludePath && shouldCollectIncludePath(S.SymInfo.Kind)) { + // Use the expansion location to get the #include header since this is + // where the symbol is exposed. + if (auto Header = + getIncludeHeader(SM, SM.getExpansionLoc(ND.getLocation()), Opts)) + Include = std::move(*Header); + } S.CompletionFilterText = FilterText; S.CompletionLabel = Label; S.CompletionPlainInsertText = PlainInsertText; @@ -259,6 +313,7 @@ Symbol::Details Detail; Detail.Documentation = Documentation; Detail.CompletionDetail = CompletionDetail; + Detail.IncludeHeader = Include; S.Detail = &Detail; Symbols.insert(S); Index: clangd/index/SymbolYAML.h =================================================================== --- clangd/index/SymbolYAML.h +++ clangd/index/SymbolYAML.h @@ -20,6 +20,7 @@ #include "Index.h" #include "llvm/Support/Error.h" +#include "llvm/Support/YAMLTraits.h" #include "llvm/Support/raw_ostream.h" namespace clang { @@ -28,9 +29,10 @@ // Read symbols from a YAML-format string. SymbolSlab SymbolsFromYAML(llvm::StringRef YAMLContent); -// Read one symbol from a YAML-format string, backed by the arena. -Symbol SymbolFromYAML(llvm::StringRef YAMLContent, - llvm::BumpPtrAllocator &Arena); +// Read one symbol from a YAML-stream. +// The arena must be the Input's context! (i.e. yaml::Input Input(Text, &Arena)) +// The returned symbol is backed by both Input and Arena. +Symbol SymbolFromYAML(llvm::yaml::Input &Input, llvm::BumpPtrAllocator &Arena); // Convert a single symbol to YAML-format string. // The YAML result is safe to concatenate. Index: clangd/index/SymbolYAML.cpp =================================================================== --- clangd/index/SymbolYAML.cpp +++ clangd/index/SymbolYAML.cpp @@ -12,7 +12,6 @@ #include "llvm/ADT/Optional.h" #include "llvm/Support/Errc.h" #include "llvm/Support/MemoryBuffer.h" -#include "llvm/Support/YAMLTraits.h" #include "llvm/Support/raw_ostream.h" LLVM_YAML_IS_DOCUMENT_LIST_VECTOR(clang::clangd::Symbol) @@ -64,6 +63,7 @@ static void mapping(IO &io, Symbol::Details &Detail) { io.mapOptional("Documentation", Detail.Documentation); io.mapOptional("CompletionDetail", Detail.CompletionDetail); + io.mapOptional("IncludeHeader", Detail.IncludeHeader); } }; @@ -175,11 +175,11 @@ return std::move(Syms).build(); } -Symbol SymbolFromYAML(llvm::StringRef YAMLContent, - llvm::BumpPtrAllocator &Arena) { - llvm::yaml::Input Yin(YAMLContent, &Arena); +Symbol SymbolFromYAML(llvm::yaml::Input &Input, llvm::BumpPtrAllocator &Arena) { + // We could grab Arena out of Input, but it'd be a huge hazard for callers. + assert(Input.getContext() == &Arena); Symbol S; - Yin >> S; + Input >> S; return S; } Index: clangd/tool/ClangdMain.cpp =================================================================== --- clangd/tool/ClangdMain.cpp +++ clangd/tool/ClangdMain.cpp @@ -16,6 +16,7 @@ #include "llvm/Support/FileSystem.h" #include "llvm/Support/Path.h" #include "llvm/Support/Program.h" +#include "llvm/Support/Signals.h" #include "llvm/Support/raw_ostream.h" #include #include @@ -58,13 +59,6 @@ llvm::cl::desc("Number of async workers used by clangd"), llvm::cl::init(getDefaultAsyncThreadsCount())); -static llvm::cl::opt EnableSnippets( - "enable-snippets", - llvm::cl::desc( - "Present snippet completions instead of plaintext completions. " - "This also enables code pattern results." /* FIXME: should it? */), - llvm::cl::init(clangd::CodeCompleteOptions().EnableSnippets)); - // FIXME: Flags are the wrong mechanism for user preferences. // We should probably read a dotfile or similar. static llvm::cl::opt IncludeIneligibleResults( @@ -141,6 +135,7 @@ llvm::cl::init(""), llvm::cl::Hidden); int main(int argc, char *argv[]) { + llvm::sys::PrintStackTraceOnErrorSignal(argv[0]); llvm::cl::ParseCommandLineOptions(argc, argv, "clangd"); if (Test) { RunSynchronously = true; @@ -237,7 +232,6 @@ if (EnableIndexBasedCompletion && !YamlSymbolFile.empty()) StaticIdx = BuildStaticIndex(YamlSymbolFile); clangd::CodeCompleteOptions CCOpts; - CCOpts.EnableSnippets = EnableSnippets; CCOpts.IncludeIneligibleResults = IncludeIneligibleResults; CCOpts.Limit = LimitCompletionResult; // Initialize and run ClangdLSPServer. Index: docs/ReleaseNotes.rst =================================================================== --- docs/ReleaseNotes.rst +++ docs/ReleaseNotes.rst @@ -88,12 +88,28 @@ Functions that have trailing returns are disallowed, except for those using decltype specifiers and lambda with otherwise unutterable return types. + +- New `readability-simd-intrinsics + `_ check + + Warns if SIMD intrinsics are used which can be replaced by + ``std::experimental::simd`` operations. - New alias `hicpp-avoid-goto `_ to `cppcoreguidelines-avoid-goto `_ added. +- New `bugprone-throw-keyword-missing + `_ check + + Diagnoses when a temporary object that appears to be an exception is constructed but not thrown. + +- New `modernize-use-uncaught-exceptions + `_ check + + Finds and replaces deprecated uses of std::uncaught_exception to std::uncaught_exceptions() + Improvements to include-fixer ----------------------------- Index: docs/clang-tidy/checks/bugprone-throw-keyword-missing.rst =================================================================== --- /dev/null +++ docs/clang-tidy/checks/bugprone-throw-keyword-missing.rst @@ -0,0 +1,21 @@ +.. title:: clang-tidy - bugprone-throw-keyword-missing + +bugprone-throw-keyword-missing +============================== + +Warns about a potentially missing ``throw`` keyword. If a temporary object is created, but the +object's type derives from (or is the same as) a class that has 'EXCEPTION', 'Exception' or +'exception' in its name, we can assume that the programmer's intention was to throw that object. + +Example: + +.. code-block:: c++ + + void f(int i) { + if (i < 0) { + // Exception is created but is not thrown. + std::runtime_error("Unexpected argument"); + } + } + + Index: docs/clang-tidy/checks/list.rst =================================================================== --- docs/clang-tidy/checks/list.rst +++ docs/clang-tidy/checks/list.rst @@ -32,6 +32,7 @@ bugprone-multiple-statement-macro bugprone-string-constructor bugprone-suspicious-memset-usage + bugprone-throw-keyword-missing bugprone-undefined-memory-manipulation bugprone-use-after-move bugprone-virtual-near-miss @@ -176,6 +177,7 @@ modernize-use-nullptr modernize-use-override modernize-use-transparent-functors + modernize-use-uncaught-exceptions modernize-use-using mpi-buffer-deref mpi-type-mismatch @@ -216,6 +218,7 @@ readability-redundant-smartptr-get readability-redundant-string-cstr readability-redundant-string-init + readability-simd-intrinsics readability-simplify-boolean-expr readability-static-accessed-through-instance readability-static-definition-in-anonymous-namespace Index: docs/clang-tidy/checks/modernize-use-uncaught-exceptions.rst =================================================================== --- /dev/null +++ docs/clang-tidy/checks/modernize-use-uncaught-exceptions.rst @@ -0,0 +1,64 @@ +.. title:: clang-tidy - modernize-use-uncaught-exceptions + +modernize-use-uncaught-exceptions +==================================== + +This check will warn on calls to ``std::uncaught_exception`` and replace them with +calls to ``std::uncaught_exceptions``, since ``std::uncaught_exception`` was deprecated +in C++17. + +Below are a few examples of what kind of occurrences will be found and what +they will be replaced with. + +.. code-block:: c++ + + #define MACRO1 std::uncaught_exception + #define MACRO2 std::uncaught_exception + + int uncaught_exception() { + return 0; + } + + int main() { + int res; + + res = uncaught_exception(); + // No warning, since it is not the deprecated function from namespace std + + res = MACRO2(); + // Warning, but will not be replaced + + res = std::uncaught_exception(); + // Warning and replaced + + using std::uncaught_exception; + // Warning and replaced + + res = uncaught_exception(); + // Warning and replaced + } + +After applying the fixes the code will look like the following: + +.. code-block:: c++ + + #define MACRO1 std::uncaught_exception + #define MACRO2 std::uncaught_exception + + int uncaught_exception() { + return 0; + } + + int main() { + int res; + + res = uncaught_exception(); + + res = MACRO2(); + + res = std::uncaught_exceptions(); + + using std::uncaught_exceptions; + + res = uncaught_exceptions(); + } Index: docs/clang-tidy/checks/readability-simd-intrinsics.rst =================================================================== --- /dev/null +++ docs/clang-tidy/checks/readability-simd-intrinsics.rst @@ -0,0 +1,44 @@ +.. title:: clang-tidy - readability-simd-intrinsics + +readability-simd-intrinsics +=========================== + +Finds SIMD intrinsics calls and suggests ``std::experimental::simd`` (`P0214`_) +alternatives. + +If the option ``Suggest`` is set to non-zero, for + +.. code-block:: c++ + + _mm_add_epi32(a, b); // x86 + vec_add(a, b); // Power + +the check suggests an alternative: ``operator+`` on ``std::experimental::simd`` +objects. + +Otherwise, it just complains the intrinsics are non-portable (and there are +`P0214`_ alternatives). + +Many architectures provide SIMD operations (e.g. x86 SSE/AVX, Power AltiVec/VSX, +ARM NEON). It is common that SIMD code implementing the same algorithm, is +written in multiple target-dispatching pieces to optimize for different +architectures or micro-architectures. + +The C++ standard proposal `P0214`_ and its extensions cover many common SIMD +operations. By migrating from target-dependent intrinsics to `P0214`_ +operations, the SIMD code can be simplified and pieces for different targets can +be unified. + +Refer to `P0214`_ for introduction and motivation for the data-parallel standard +library. + +Options +------- + +.. option:: Suggest + + If this option is set to non-zero (default is `0`), the check will suggest + `P0214`_ alternatives, otherwise it only points out the intrinsic function is + non-portable. + +.. _P0214: http://wg21.link/p0214 Index: include-fixer/find-all-symbols/STLPostfixHeaderMap.cpp =================================================================== --- include-fixer/find-all-symbols/STLPostfixHeaderMap.cpp +++ include-fixer/find-all-symbols/STLPostfixHeaderMap.cpp @@ -211,6 +211,10 @@ {"cwctype$", ""}, {"cxxabi.h$", ""}, {"debug/debug.h$", ""}, + {"debug/map.h$", ""}, + {"debug/multimap.h$", ""}, + {"debug/multiset.h$", ""}, + {"debug/set.h$", ""}, {"deque$", ""}, {"exception$", ""}, {"ext/alloc_traits.h$", ""}, Index: include-fixer/tool/CMakeLists.txt =================================================================== --- include-fixer/tool/CMakeLists.txt +++ include-fixer/tool/CMakeLists.txt @@ -1,6 +1,6 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) -add_clang_executable(clang-include-fixer +add_clang_tool(clang-include-fixer ClangIncludeFixer.cpp ) Index: test/clang-tidy/bugprone-throw-keyword-missing.cpp =================================================================== --- /dev/null +++ test/clang-tidy/bugprone-throw-keyword-missing.cpp @@ -0,0 +1,167 @@ +// RUN: %check_clang_tidy %s bugprone-throw-keyword-missing %t -- -- -fexceptions + +namespace std { + +// std::string declaration (taken from test/clang-tidy/readability-redundant-string-cstr-msvc.cpp). +template +class allocator {}; +template +class char_traits {}; +template , typename A = std::allocator> +struct basic_string { + basic_string(); + basic_string(const basic_string &); + // MSVC headers define two constructors instead of using optional arguments. + basic_string(const C *); + basic_string(const C *, const A &); + ~basic_string(); +}; +typedef basic_string string; +typedef basic_string wstring; + +// std::exception and std::runtime_error declaration. +struct exception { + exception(); + exception(const exception &other); + virtual ~exception(); +}; + +struct runtime_error : public exception { + explicit runtime_error(const std::string &what_arg); +}; + +} // namespace std + +// The usage of this class should never emit a warning. +struct RegularClass {}; + +// Class name contains the substring "exception", in certain cases using this class should emit a warning. +struct RegularException { + RegularException() {} + + // Constructors with a single argument are treated differently (cxxFunctionalCastExpr). + RegularException(int) {} +}; + +// -------------- + +void stdExceptionNotTrownTest(int i) { + if (i < 0) + // CHECK-MESSAGES: :[[@LINE+1]]:5: warning: suspicious exception object created but not thrown; did you mean 'throw {{.*}}'? [bugprone-throw-keyword-missing] + std::exception(); + + if (i > 0) + // CHECK-MESSAGES: :[[@LINE+1]]:5: warning: suspicious exception + std::runtime_error("Unexpected argument"); +} + +void stdExceptionThrownTest(int i) { + if (i < 0) + throw std::exception(); + + if (i > 0) + throw std::runtime_error("Unexpected argument"); +} + +void regularClassNotThrownTest(int i) { + if (i < 0) + RegularClass(); +} + +void regularClassThrownTest(int i) { + if (i < 0) + throw RegularClass(); +} + +void nameContainsExceptionNotThrownTest(int i) { + if (i < 0) + // CHECK-MESSAGES: :[[@LINE+1]]:5: warning: suspicious exception + RegularException(); + + if (i > 0) + // CHECK-MESSAGES: :[[@LINE+1]]:5: warning: suspicious exception + RegularException(5); +} + +void nameContainsExceptionThrownTest(int i) { + if (i < 0) + throw RegularException(); + + if (i > 0) + throw RegularException(5); +} + +template +void f(int i, Exception excToBeThrown) {} + +void funcCallWithTempExcTest() { + f(5, RegularException()); +} + +// Global variable initilization test. +RegularException exc = RegularException(); +RegularException *excptr = new RegularException(); + +void localVariableInitTest() { + RegularException exc = RegularException(); + RegularException *excptr = new RegularException(); +} + +class CtorInitializerListTest { + RegularException exc; + + CtorInitializerListTest() : exc(RegularException()) {} + + CtorInitializerListTest(int) try : exc(RegularException()) { + // Constructor body + } catch (...) { + // CHECK-MESSAGES: :[[@LINE+1]]:5: warning: suspicious exception + RegularException(); + } + + CtorInitializerListTest(float); +}; + +CtorInitializerListTest::CtorInitializerListTest(float) try : exc(RegularException()) { + // Constructor body +} catch (...) { + // CHECK-MESSAGES: :[[@LINE+1]]:3: warning: suspicious exception + RegularException(); +} + +RegularException funcReturningExceptionTest(int i) { + return RegularException(); +} + +void returnedValueTest() { + funcReturningExceptionTest(3); +} + +struct ClassBracedInitListTest { + ClassBracedInitListTest(RegularException exc) {} +}; + +void foo(RegularException, ClassBracedInitListTest) {} + +void bracedInitListTest() { + RegularException exc{}; + ClassBracedInitListTest test = {RegularException()}; + foo({}, {RegularException()}); +} + +typedef std::exception ERROR_BASE; +class RegularError : public ERROR_BASE {}; + +void typedefTest() { + // CHECK-MESSAGES: :[[@LINE+1]]:3: warning: suspicious exception + RegularError(); +} + +struct ExceptionRAII { + ExceptionRAII() {} + ~ExceptionRAII() {} +}; + +void exceptionRAIITest() { + ExceptionRAII E; +} Index: test/clang-tidy/modernize-use-uncaught-exceptions.cpp =================================================================== --- /dev/null +++ test/clang-tidy/modernize-use-uncaught-exceptions.cpp @@ -0,0 +1,79 @@ +// RUN: %check_clang_tidy %s modernize-use-uncaught-exceptions %t -- -- -std=c++1z +#define MACRO std::uncaught_exception +// CHECK-FIXES: #define MACRO std::uncaught_exception + +bool uncaught_exception() { + return 0; +} + +namespace std { + bool uncaught_exception() { + return false; + } + + int uncaught_exceptions() { + return 0; + } +} + +template +bool doSomething(T t) { + return t(); + // CHECK-FIXES: return t(); +} + +template +bool doSomething2() { + return T(); + // CHECK-MESSAGES: [[@LINE-1]]:10: warning: 'std::uncaught_exception' is deprecated, use 'std::uncaught_exceptions' instead + // CHECK-FIXES: return T(); +} + +void no_warn() { + + uncaught_exception(); + // CHECK-FIXES: uncaught_exception(); + + doSomething(uncaught_exception); + // CHECK-FIXES: doSomething(uncaught_exception); +} + +void warn() { + + std::uncaught_exception(); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: 'std::uncaught_exception' is deprecated, use 'std::uncaught_exceptions' instead + // CHECK-FIXES: std::uncaught_exceptions(); + + using std::uncaught_exception; + // CHECK-MESSAGES: [[@LINE-1]]:14: warning: 'std::uncaught_exception' is deprecated, use 'std::uncaught_exceptions' instead + // CHECK-FIXES: using std::uncaught_exceptions; + + uncaught_exception(); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: 'std::uncaught_exception' is deprecated, use 'std::uncaught_exceptions' instead + // CHECK-FIXES: uncaught_exceptions(); + + bool b{uncaught_exception()}; + // CHECK-MESSAGES: [[@LINE-1]]:10: warning: 'std::uncaught_exception' is deprecated, use 'std::uncaught_exceptions' instead + // CHECK-FIXES: bool b{std::uncaught_exceptions() > 0}; + + MACRO(); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: 'std::uncaught_exception' is deprecated, use 'std::uncaught_exceptions' instead + // CHECK-FIXES: MACRO(); + + doSomething(std::uncaught_exception); + // CHECK-MESSAGES: [[@LINE-1]]:15: warning: 'std::uncaught_exception' is deprecated, use 'std::uncaught_exceptions' instead + // CHECK-FIXES: doSomething(std::uncaught_exception); + + doSomething(uncaught_exception); + // CHECK-MESSAGES: [[@LINE-1]]:15: warning: 'std::uncaught_exception' is deprecated, use 'std::uncaught_exceptions' instead + // CHECK-FIXES: doSomething(uncaught_exception); + + bool (*foo)(); + foo = &uncaught_exception; + // CHECK-MESSAGES: [[@LINE-1]]:10: warning: 'std::uncaught_exception' is deprecated, use 'std::uncaught_exceptions' instead + // CHECK-FIXES: foo = &uncaught_exception; + + doSomething2(); + // CHECK-MESSAGES: [[@LINE-1]]:16: warning: 'std::uncaught_exception' is deprecated, use 'std::uncaught_exceptions' instead + // CHECK-FIXES: doSomething2(); +} Index: test/clang-tidy/readability-simd-intrinsics-ppc.cpp =================================================================== --- /dev/null +++ test/clang-tidy/readability-simd-intrinsics-ppc.cpp @@ -0,0 +1,13 @@ +// RUN: %check_clang_tidy %s readability-simd-intrinsics %t -- \ +// RUN: -config='{CheckOptions: [ \ +// RUN: {key: readability-simd-intrinsics.Suggest, value: 1} \ +// RUN: ]}' -- -target ppc64le -maltivec -std=c++11 + +vector int vec_add(vector int, vector int); + +void PPC() { + vector int i0, i1; + + vec_add(i0, i1); +// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: 'vec_add' can be replaced by operator+ on std::experimental::simd objects [readability-simd-intrinsics] +} Index: test/clang-tidy/readability-simd-intrinsics-x86.cpp =================================================================== --- /dev/null +++ test/clang-tidy/readability-simd-intrinsics-x86.cpp @@ -0,0 +1,25 @@ +// RUN: %check_clang_tidy %s readability-simd-intrinsics %t -- \ +// RUN: -config='{CheckOptions: [ \ +// RUN: {key: readability-simd-intrinsics.Suggest, value: 1} \ +// RUN: ]}' -- -target x86_64 -std=c++11 + +typedef long long __m128i __attribute__((vector_size(16))); +typedef double __m256 __attribute__((vector_size(32))); + +__m128i _mm_add_epi32(__m128i, __m128i); +__m256 _mm256_load_pd(double const *); +void _mm256_store_pd(double *, __m256); + +int _mm_add_fake(int, int); + +void X86() { + __m128i i0, i1; + __m256 d0; + + _mm_add_epi32(i0, i1); +// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: '_mm_add_epi32' can be replaced by operator+ on std::experimental::simd objects [readability-simd-intrinsics] + d0 = _mm256_load_pd(0); + _mm256_store_pd(0, d0); + + _mm_add_fake(0, 1); +} Index: test/clangd/completion-snippets.test =================================================================== --- /dev/null +++ test/clangd/completion-snippets.test @@ -0,0 +1,41 @@ +# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s +# RUN: clangd -lit-test -pch-storage=memory < %s | FileCheck -strict-whitespace %s +{ + "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":"test:///main.cpp","languageId":"cpp","version":1,"text":"int func_with_args(int a, int b);\nint main() {\nfunc_with\n}"}}} +--- +{"jsonrpc":"2.0","id":1,"method":"textDocument/completion","params":{"textDocument":{"uri":"test:///main.cpp"},"position":{"line":2,"character":7}}} +# CHECK: "id": 1 +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": { +# CHECK-NEXT: "isIncomplete": {{.*}} +# CHECK-NEXT: "items": [ +# CHECK: "filterText": "func_with_args", +# CHECK-NEXT: "insertText": "func_with_args(${1:int a}, ${2:int b})", +# CHECK-NEXT: "insertTextFormat": 2, +# CHECK-NEXT: "kind": 3, +# CHECK-NEXT: "label": "func_with_args(int a, int b)", +# CHECK-NEXT: "sortText": "{{.*}}func_with_args" +# CHECK-NEXT: } +# CHECK-NEXT: ] +# CHECK-NEXT: } +--- +{"jsonrpc":"2.0","id":4,"method":"shutdown"} Index: test/clangd/hover.test =================================================================== --- /dev/null +++ test/clangd/hover.test @@ -0,0 +1,19 @@ +# RUN: clangd -lit-test < %s | FileCheck %s +{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} +--- +{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///main.cpp","languageId":"cpp","version":1,"text":"void foo(); int main() { foo(); }\n"}}} +--- +{"jsonrpc":"2.0","id":1,"method":"textDocument/hover","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":0,"character":27}}} +# CHECK: "id": 1, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": { +# CHECK-NEXT: "contents": { +# CHECK-NEXT: "kind": "plaintext", +# CHECK-NEXT: "value": "Declared in global namespace\n\nvoid foo()" +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT:} +--- +{"jsonrpc":"2.0","id":3,"method":"shutdown"} +--- +{"jsonrpc":"2.0","method":"exit"} Index: test/clangd/initialize-params-invalid.test =================================================================== --- test/clangd/initialize-params-invalid.test +++ test/clangd/initialize-params-invalid.test @@ -24,9 +24,11 @@ # CHECK-NEXT: "documentRangeFormattingProvider": true, # CHECK-NEXT: "executeCommandProvider": { # CHECK-NEXT: "commands": [ -# CHECK-NEXT: "clangd.applyFix" +# CHECK-NEXT: "clangd.applyFix", +# CHECK-NEXT: "clangd.insertInclude" # CHECK-NEXT: ] # CHECK-NEXT: }, +# CHECK-NEXT: "hoverProvider": true, # CHECK-NEXT: "renameProvider": true, # CHECK-NEXT: "signatureHelpProvider": { # CHECK-NEXT: "triggerCharacters": [ Index: test/clangd/initialize-params.test =================================================================== --- test/clangd/initialize-params.test +++ test/clangd/initialize-params.test @@ -24,9 +24,11 @@ # CHECK-NEXT: "documentRangeFormattingProvider": true, # CHECK-NEXT: "executeCommandProvider": { # CHECK-NEXT: "commands": [ -# CHECK-NEXT: "clangd.applyFix" +# CHECK-NEXT: "clangd.applyFix", +# CHECK-NEXT: "clangd.insertInclude" # CHECK-NEXT: ] # CHECK-NEXT: }, +# CHECK-NEXT: "hoverProvider": true, # CHECK-NEXT: "renameProvider": true, # CHECK-NEXT: "signatureHelpProvider": { # CHECK-NEXT: "triggerCharacters": [ Index: test/clangd/insert-include.test =================================================================== --- /dev/null +++ test/clangd/insert-include.test @@ -0,0 +1,36 @@ +# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s +# RUN: clangd -lit-test -pch-storage=memory < %s | FileCheck -strict-whitespace %s +{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} +--- +{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///main.cpp","languageId":"cpp","version":1,"text":"void f() {}"}}} +--- +{"jsonrpc":"2.0","id":3,"method":"workspace/executeCommand","params":{"arguments":[{"header":"","textDocument":{"uri":"test:///main.cpp"}}],"command":"clangd.insertInclude"}} +# CHECK: "id": 3, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": "Inserted header " +# CHECK-NEXT: } +# CHECK: "method": "workspace/applyEdit", +# CHECK-NEXT: "params": { +# CHECK-NEXT: "edit": { +# CHECK-NEXT: "changes": { +# CHECK-NEXT: "file:///clangd-test/main.cpp": [ +# CHECK-NEXT: { +# CHECK-NEXT: "newText": "#include \n", +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 0, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 0, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: ] +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: } +--- +{"jsonrpc":"2.0","id":4,"method":"shutdown"} Index: test/clangd/trace.test =================================================================== --- test/clangd/trace.test +++ test/clangd/trace.test @@ -1,19 +1,23 @@ -# RUN: CLANGD_TRACE=%t clangd -lit-test < %s && FileCheck %s < %t +# RUN: env CLANGD_TRACE=%t clangd -lit-test < %s && FileCheck %s < %t {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} --- {"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c","version":1,"text":"void main() {}"}}} -# CHECK: {"displayTimeUnit":"ns","traceEvents":[ -# Start opening the doc. -# CHECK: "name": "textDocument/didOpen" -# CHECK: "ph": "B" -# Start building the preamble. -# CHECK: "name": "Preamble" -# CHECK: }, -# Finish building the preamble, with filename. -# CHECK: "File": "{{.*(/|\\)}}foo.c" -# CHECK-NEXT: }, -# CHECK-NEXT: "ph": "E" -# Start building the file. -# CHECK: "name": "Build" +# These assertions are a bit loose, to avoid brittleness. +# CHECK: {"displayTimeUnit":"ns","traceEvents":[ +# CHECK: { +# CHECK: "args": { +# CHECK: "File": "{{.*(/|\\)}}foo.c" +# CHECK: }, +# CHECK: "name": "Preamble", +# CHECK: "ph": "X", +# CHECK: } +# CHECK: { +# CHECK: "args": { +# CHECK: "File": "{{.*(/|\\)}}foo.c" +# CHECK: }, +# CHECK: "name": "Build", +# CHECK: "ph": "X", +# CHECK: } +# CHECK: }, --- {"jsonrpc":"2.0","id":5,"method":"shutdown"} Index: unittests/clangd/CMakeLists.txt =================================================================== --- unittests/clangd/CMakeLists.txt +++ unittests/clangd/CMakeLists.txt @@ -17,6 +17,7 @@ ContextTests.cpp FileIndexTests.cpp FuzzyMatchTests.cpp + HeadersTests.cpp IndexTests.cpp JSONExprTests.cpp SourceCodeTests.cpp @@ -37,6 +38,7 @@ clangFormat clangFrontend clangIndex + clangLex clangSema clangTooling clangToolingCore Index: unittests/clangd/ClangdTests.cpp =================================================================== --- unittests/clangd/ClangdTests.cpp +++ unittests/clangd/ClangdTests.cpp @@ -7,11 +7,13 @@ // //===----------------------------------------------------------------------===// +#include "Annotations.h" #include "ClangdLSPServer.h" #include "ClangdServer.h" #include "Matchers.h" #include "SyncAPI.h" #include "TestFS.h" +#include "URI.h" #include "clang/Config/config.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringMap.h" @@ -76,6 +78,39 @@ VFSTag LastVFSTag = VFSTag(); }; +// For each file, record whether the last published diagnostics contained at +// least one error. +class MultipleErrorCHeckingDiagConsumer : public DiagnosticsConsumer { +public: + void + onDiagnosticsReady(PathRef File, + Tagged> Diagnostics) override { + bool HadError = diagsContainErrors(Diagnostics.Value); + + std::lock_guard Lock(Mutex); + LastDiagsHadError[File] = HadError; + } + + bool contains(PathRef P) { + std::lock_guard Lock(Mutex); + return LastDiagsHadError.find(P) != LastDiagsHadError.end(); + } + + bool lastHadError(PathRef P) { + std::lock_guard Lock(Mutex); + return LastDiagsHadError[P]; + } + + void clear() { + std::lock_guard Lock(Mutex); + LastDiagsHadError.clear(); + } + +private: + std::mutex Mutex; + std::map LastDiagsHadError; +}; + /// Replaces all patterns of the form 0x123abc with spaces std::string replacePtrsInDump(std::string const &Dump) { llvm::Regex RE("0x[0-9a-fA-F]+"); @@ -96,12 +131,10 @@ } std::string dumpASTWithoutMemoryLocs(ClangdServer &Server, PathRef File) { - auto DumpWithMemLocs = Server.dumpAST(File); + auto DumpWithMemLocs = runDumpAST(Server, File); return replacePtrsInDump(DumpWithMemLocs); } -} // namespace - class ClangdVFSTest : public ::testing::Test { protected: std::string parseSourceAndDumpAST( @@ -114,13 +147,10 @@ ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount(), /*StorePreamblesInMemory=*/true); for (const auto &FileWithContents : ExtraFiles) - FS.Files[getVirtualTestFilePath(FileWithContents.first)] = - FileWithContents.second; - - auto SourceFilename = getVirtualTestFilePath(SourceFileRelPath); + FS.Files[testPath(FileWithContents.first)] = FileWithContents.second; + auto SourceFilename = testPath(SourceFileRelPath); FS.ExpectedFile = SourceFilename; - Server.addDocument(SourceFilename, SourceContents); auto Result = dumpASTWithoutMemoryLocs(Server, SourceFilename); EXPECT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics"; @@ -176,10 +206,9 @@ int b = a; )cpp"; - auto FooCpp = getVirtualTestFilePath("foo.cpp"); - auto FooH = getVirtualTestFilePath("foo.h"); + auto FooCpp = testPath("foo.cpp"); - FS.Files[FooH] = "int a;"; + FS.Files[testPath("foo.h")] = "int a;"; FS.Files[FooCpp] = SourceContents; FS.ExpectedFile = FooCpp; @@ -215,8 +244,8 @@ int b = a; )cpp"; - auto FooCpp = getVirtualTestFilePath("foo.cpp"); - auto FooH = getVirtualTestFilePath("foo.h"); + auto FooCpp = testPath("foo.cpp"); + auto FooH = testPath("foo.h"); FS.Files[FooH] = "int a;"; FS.Files[FooCpp] = SourceContents; @@ -252,7 +281,7 @@ /*AsyncThreadsCount=*/0, /*StorePreamblesInMemory=*/true); - auto FooCpp = getVirtualTestFilePath("foo.cpp"); + auto FooCpp = testPath("foo.cpp"); const auto SourceContents = "int a;"; FS.Files[FooCpp] = SourceContents; FS.ExpectedFile = FooCpp; @@ -309,7 +338,7 @@ llvm::sys::path::append(StringPath, IncludeDir, "string"); FS.Files[StringPath] = "class mock_string {};"; - auto FooCpp = getVirtualTestFilePath("foo.cpp"); + auto FooCpp = testPath("foo.cpp"); const auto SourceContents = R"cpp( #include mock_string x; @@ -340,7 +369,7 @@ // No need to sync reparses, because reparses are performed on the calling // thread. - auto FooCpp = getVirtualTestFilePath("foo.cpp"); + auto FooCpp = testPath("foo.cpp"); const auto SourceContents1 = R"cpp( template struct foo { T x; }; @@ -378,6 +407,118 @@ EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags()); } +TEST_F(ClangdVFSTest, ForceReparseCompileCommandDefines) { + MockFSProvider FS; + ErrorCheckingDiagConsumer DiagConsumer; + MockCompilationDatabase CDB; + ClangdServer Server(CDB, DiagConsumer, FS, + /*AsyncThreadsCount=*/0, + /*StorePreamblesInMemory=*/true); + + // No need to sync reparses, because reparses are performed on the calling + // thread. + auto FooCpp = testPath("foo.cpp"); + const auto SourceContents = R"cpp( +#ifdef WITH_ERROR +this +#endif + +int main() { return 0; } +)cpp"; + FS.Files[FooCpp] = ""; + FS.ExpectedFile = FooCpp; + + // Parse with define, we expect to see the errors. + CDB.ExtraClangFlags = {"-DWITH_ERROR"}; + Server.addDocument(FooCpp, SourceContents); + EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags()); + + // Parse without the define, no errors should be produced. + CDB.ExtraClangFlags = {}; + // Currently, addDocument never checks if CompileCommand has changed, so we + // expect to see the errors. + Server.addDocument(FooCpp, SourceContents); + EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags()); + // But forceReparse should reparse the file with proper flags. + Server.forceReparse(FooCpp); + EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags()); + // Subsequent addDocument call should finish without errors too. + Server.addDocument(FooCpp, SourceContents); + EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags()); +} + +// Test ClangdServer.reparseOpenedFiles. +TEST_F(ClangdVFSTest, ReparseOpenedFiles) { + Annotations FooSource(R"cpp( +#ifdef MACRO +$one[[static void bob() {}]] +#else +$two[[static void bob() {}]] +#endif + +int main () { bo^b (); return 0; } +)cpp"); + + Annotations BarSource(R"cpp( +#ifdef MACRO +this is an error +#endif +)cpp"); + + Annotations BazSource(R"cpp( +int hello; +)cpp"); + + MockFSProvider FS; + MockCompilationDatabase CDB; + MultipleErrorCHeckingDiagConsumer DiagConsumer; + ClangdServer Server(CDB, DiagConsumer, FS, + /*AsyncThreadsCount=*/0, + /*StorePreamblesInMemory=*/true); + + auto FooCpp = testPath("foo.cpp"); + auto BarCpp = testPath("bar.cpp"); + auto BazCpp = testPath("baz.cpp"); + + FS.Files[FooCpp] = ""; + FS.Files[BarCpp] = ""; + FS.Files[BazCpp] = ""; + + CDB.ExtraClangFlags = {"-DMACRO=1"}; + Server.addDocument(FooCpp, FooSource.code()); + Server.addDocument(BarCpp, BarSource.code()); + Server.addDocument(BazCpp, BazSource.code()); + + EXPECT_TRUE(DiagConsumer.contains(FooCpp)); + EXPECT_TRUE(DiagConsumer.contains(BarCpp)); + EXPECT_TRUE(DiagConsumer.contains(BazCpp)); + EXPECT_FALSE(DiagConsumer.lastHadError(FooCpp)); + EXPECT_TRUE(DiagConsumer.lastHadError(BarCpp)); + EXPECT_FALSE(DiagConsumer.lastHadError(BazCpp)); + + auto Locations = runFindDefinitions(Server, FooCpp, FooSource.point()); + EXPECT_TRUE(bool(Locations)); + EXPECT_THAT(Locations->Value, ElementsAre(Location{URIForFile{FooCpp}, + FooSource.range("one")})); + + // Undefine MACRO, close baz.cpp. + CDB.ExtraClangFlags.clear(); + DiagConsumer.clear(); + Server.removeDocument(BazCpp); + Server.reparseOpenedFiles(); + + EXPECT_TRUE(DiagConsumer.contains(FooCpp)); + EXPECT_TRUE(DiagConsumer.contains(BarCpp)); + EXPECT_FALSE(DiagConsumer.contains(BazCpp)); + EXPECT_FALSE(DiagConsumer.lastHadError(FooCpp)); + EXPECT_FALSE(DiagConsumer.lastHadError(BarCpp)); + + Locations = runFindDefinitions(Server, FooCpp, FooSource.point()); + EXPECT_TRUE(bool(Locations)); + EXPECT_THAT(Locations->Value, ElementsAre(Location{URIForFile{FooCpp}, + FooSource.range("two")})); +} + TEST_F(ClangdVFSTest, MemoryUsage) { MockFSProvider FS; ErrorCheckingDiagConsumer DiagConsumer; @@ -388,13 +529,13 @@ // No need to sync reparses, because reparses are performed on the calling // thread. - Path FooCpp = getVirtualTestFilePath("foo.cpp").str(); + Path FooCpp = testPath("foo.cpp"); const auto SourceContents = R"cpp( struct Something { int method(); }; )cpp"; - Path BarCpp = getVirtualTestFilePath("bar.cpp").str(); + Path BarCpp = testPath("bar.cpp"); FS.Files[FooCpp] = ""; FS.Files[BarCpp] = ""; @@ -423,26 +564,26 @@ /*AsyncThreadsCount=*/0, /*StorePreamblesInMemory=*/true); - auto FooCpp = getVirtualTestFilePath("foo.cpp"); + auto FooCpp = testPath("foo.cpp"); // clang cannot create CompilerInvocation if we pass two files in the // CompileCommand. We pass the file in ExtraFlags once and CDB adds another // one in getCompileCommand(). - CDB.ExtraClangFlags.push_back(FooCpp.str()); + CDB.ExtraClangFlags.push_back(FooCpp); // Clang can't parse command args in that case, but we shouldn't crash. Server.addDocument(FooCpp, "int main() {}"); - EXPECT_EQ(Server.dumpAST(FooCpp), ""); - EXPECT_ERROR(Server.findDefinitions(FooCpp, Position())); - EXPECT_ERROR(Server.findDocumentHighlights(FooCpp, Position())); - EXPECT_ERROR(Server.rename(FooCpp, Position(), "new_name")); + EXPECT_EQ(runDumpAST(Server, FooCpp), ""); + EXPECT_ERROR(runFindDefinitions(Server, FooCpp, Position())); + EXPECT_ERROR(runFindDocumentHighlights(Server, FooCpp, Position())); + EXPECT_ERROR(runRename(Server, FooCpp, Position(), "new_name")); // FIXME: codeComplete and signatureHelp should also return errors when they // can't parse the file. EXPECT_THAT( runCodeComplete(Server, FooCpp, Position(), clangd::CodeCompleteOptions()) .Value.items, IsEmpty()); - auto SigHelp = Server.signatureHelp(FooCpp, Position()); + auto SigHelp = runSignatureHelp(Server, FooCpp, Position()); ASSERT_TRUE(bool(SigHelp)) << "signatureHelp returned an error"; EXPECT_THAT(SigHelp->Value.signatures, IsEmpty()); } @@ -478,16 +619,13 @@ unsigned MaxLineForFileRequests = 7; unsigned MaxColumnForFileRequests = 10; - std::vector> FilePaths; - FilePaths.reserve(FilesCount); - for (unsigned I = 0; I < FilesCount; ++I) - FilePaths.push_back(getVirtualTestFilePath(std::string("Foo") + - std::to_string(I) + ".cpp")); - // Mark all of those files as existing. + std::vector FilePaths; MockFSProvider FS; - for (auto &&FilePath : FilePaths) - FS.Files[FilePath] = ""; - + for (unsigned I = 0; I < FilesCount; ++I) { + std::string Name = std::string("Foo") + std::to_string(I) + ".cpp"; + FS.Files[Name] = ""; + FilePaths.push_back(testPath(Name)); + } struct FileStat { unsigned HitsWithoutErrors = 0; @@ -646,7 +784,7 @@ Pos.line = LineDist(RandGen); Pos.character = ColumnDist(RandGen); - ASSERT_TRUE(!!Server.findDefinitions(FilePaths[FileIndex], Pos)); + ASSERT_TRUE(!!runFindDefinitions(Server, FilePaths[FileIndex], Pos)); }; std::vector> AsyncRequests = { @@ -698,9 +836,9 @@ int b = a; )cpp"; - auto FooCpp = getVirtualTestFilePath("foo.cpp"); - auto FooH = getVirtualTestFilePath("foo.h"); - auto Invalid = getVirtualTestFilePath("main.cpp"); + auto FooCpp = testPath("foo.cpp"); + auto FooH = testPath("foo.h"); + auto Invalid = testPath("main.cpp"); FS.Files[FooCpp] = SourceContents; FS.Files[FooH] = "int a;"; @@ -721,8 +859,8 @@ // Test with header file in capital letters and different extension, source // file with different extension - auto FooC = getVirtualTestFilePath("bar.c"); - auto FooHH = getVirtualTestFilePath("bar.HH"); + auto FooC = testPath("bar.c"); + auto FooHH = testPath("bar.HH"); FS.Files[FooC] = SourceContents; FS.Files[FooHH] = "int a;"; @@ -732,8 +870,8 @@ ASSERT_EQ(PathResult.getValue(), FooHH); // Test with both capital letters - auto Foo2C = getVirtualTestFilePath("foo2.C"); - auto Foo2HH = getVirtualTestFilePath("foo2.HH"); + auto Foo2C = testPath("foo2.C"); + auto Foo2HH = testPath("foo2.HH"); FS.Files[Foo2C] = SourceContents; FS.Files[Foo2HH] = "int a;"; @@ -742,8 +880,8 @@ ASSERT_EQ(PathResult.getValue(), Foo2HH); // Test with source file as capital letter and .hxx header file - auto Foo3C = getVirtualTestFilePath("foo3.C"); - auto Foo3HXX = getVirtualTestFilePath("foo3.hxx"); + auto Foo3C = testPath("foo3.C"); + auto Foo3HXX = testPath("foo3.hxx"); SourceContents = R"c( #include "foo3.hxx" @@ -768,8 +906,8 @@ public: std::atomic Count = {0}; - NoConcurrentAccessDiagConsumer(std::promise StartSecondReparse) - : StartSecondReparse(std::move(StartSecondReparse)) {} + NoConcurrentAccessDiagConsumer(std::promise StartSecondReparse) + : StartSecondReparse(std::move(StartSecondReparse)) {} void onDiagnosticsReady(PathRef, Tagged>) override { @@ -808,7 +946,7 @@ int d; )cpp"; - auto FooCpp = getVirtualTestFilePath("foo.cpp"); + auto FooCpp = testPath("foo.cpp"); MockFSProvider FS; FS.Files[FooCpp] = ""; @@ -826,5 +964,38 @@ ASSERT_EQ(DiagConsumer.Count, 2); // Sanity check - we actually ran both? } +TEST_F(ClangdVFSTest, InsertIncludes) { + MockFSProvider FS; + ErrorCheckingDiagConsumer DiagConsumer; + MockCompilationDatabase CDB; + ClangdServer Server(CDB, DiagConsumer, FS, + /*AsyncThreadsCount=*/0, + /*StorePreamblesInMemory=*/true); + + // No need to sync reparses, because reparses are performed on the calling + // thread. + auto FooCpp = testPath("foo.cpp"); + const auto Code = R"cpp( +#include "x.h" + +void f() {} +)cpp"; + FS.Files[FooCpp] = Code; + Server.addDocument(FooCpp, Code); + + auto Inserted = [&](llvm::StringRef Header, llvm::StringRef Expected) { + auto Replaces = Server.insertInclude(FooCpp, Code, Header); + EXPECT_TRUE(static_cast(Replaces)); + auto Changed = tooling::applyAllReplacements(Code, *Replaces); + EXPECT_TRUE(static_cast(Changed)); + return llvm::StringRef(*Changed).contains( + (llvm::Twine("#include ") + Expected + "").str()); + }; + + EXPECT_TRUE(Inserted("\"y.h\"", "\"y.h\"")); + EXPECT_TRUE(Inserted("", "")); +} + +} // namespace } // namespace clangd } // namespace clang Index: unittests/clangd/CodeCompleteTests.cpp =================================================================== --- unittests/clangd/CodeCompleteTests.cpp +++ unittests/clangd/CodeCompleteTests.cpp @@ -119,7 +119,7 @@ IgnoreDiagnostics DiagConsumer; ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount(), /*StorePreamblesInMemory=*/true); - auto File = getVirtualTestFilePath("foo.cpp"); + auto File = testPath("foo.cpp"); Annotations Test(Text); Server.addDocument(File, Test.code()); EXPECT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for preamble"; @@ -344,7 +344,7 @@ MockCompilationDatabase CDB; ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount(), /*StorePreamblesInMemory=*/true); - auto File = getVirtualTestFilePath("foo.cpp"); + auto File = testPath("foo.cpp"); Server.addDocument(File, "ignored text!"); Annotations Example("int cbc; int b = ^;"); @@ -543,9 +543,9 @@ ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount(), /*StorePreamblesInMemory=*/true); - FS.Files[getVirtualTestFilePath("bar.h")] = + FS.Files[testPath("bar.h")] = R"cpp(namespace ns { struct preamble { int member; }; })cpp"; - auto File = getVirtualTestFilePath("foo.cpp"); + auto File = testPath("foo.cpp"); Annotations Test(R"cpp( #include "bar.h" namespace ns { int local; } @@ -580,12 +580,15 @@ /*StorePreamblesInMemory=*/true, /*BuildDynamicSymbolIndex=*/true); - Server.addDocument(getVirtualTestFilePath("foo.cpp"), R"cpp( + FS.Files[testPath("foo.h")] = R"cpp( namespace ns { class XYZ {}; void foo(int x) {} } + )cpp"; + Server.addDocument(testPath("foo.cpp"), R"cpp( + #include "foo.h" )cpp"); ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for preamble"; - auto File = getVirtualTestFilePath("bar.cpp"); + auto File = testPath("bar.cpp"); Annotations Test(R"cpp( namespace ns { class XXX {}; @@ -622,11 +625,11 @@ IgnoreDiagnostics DiagConsumer; ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount(), /*StorePreamblesInMemory=*/true); - auto File = getVirtualTestFilePath("foo.cpp"); + auto File = testPath("foo.cpp"); Annotations Test(Text); Server.addDocument(File, Test.code()); EXPECT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for preamble"; - auto R = Server.signatureHelp(File, Test.point()); + auto R = runSignatureHelp(Server, File, Test.point()); assert(R); return R.get().Value; } @@ -698,7 +701,7 @@ fuzzyFind(const FuzzyFindRequest &Req, llvm::function_ref Callback) const override { Requests.push_back(Req); - return false; + return true; } const std::vector allRequests() const { return Requests; } Index: unittests/clangd/FileIndexTests.cpp =================================================================== --- unittests/clangd/FileIndexTests.cpp +++ unittests/clangd/FileIndexTests.cpp @@ -7,6 +7,7 @@ // //===----------------------------------------------------------------------===// +#include "TestFS.h" #include "index/FileIndex.h" #include "clang/Frontend/CompilerInvocation.h" #include "clang/Frontend/PCHContainerOperations.h" @@ -83,17 +84,28 @@ } /// Create an ParsedAST for \p Code. Returns None if \p Code is empty. -llvm::Optional build(std::string Path, llvm::StringRef Code) { +/// \p Code is put into .h which is included by \p .cpp. +llvm::Optional build(llvm::StringRef BasePath, + llvm::StringRef Code) { if (Code.empty()) return llvm::None; - const char *Args[] = {"clang", "-xc++", Path.c_str()}; + + assert(llvm::sys::path::extension(BasePath).empty() && + "BasePath must be a base file path without extension."); + llvm::IntrusiveRefCntPtr VFS( + new vfs::InMemoryFileSystem); + std::string Path = (BasePath + ".cpp").str(); + std::string Header = (BasePath + ".h").str(); + VFS->addFile(Path, 0, llvm::MemoryBuffer::getMemBuffer("")); + VFS->addFile(Header, 0, llvm::MemoryBuffer::getMemBuffer(Code)); + const char *Args[] = {"clang", "-xc++", "-include", Header.c_str(), + Path.c_str()}; auto CI = createInvocationFromCommandLine(Args); auto Buf = llvm::MemoryBuffer::getMemBuffer(Code); auto AST = ParsedAST::Build(std::move(CI), nullptr, std::move(Buf), - std::make_shared(), - vfs::getRealFileSystem()); + std::make_shared(), VFS); assert(AST.hasValue()); return std::move(*AST); } @@ -169,6 +181,20 @@ EXPECT_THAT(match(M, Req), UnorderedElementsAre("X")); } +#ifndef LLVM_ON_WIN32 +TEST(FileIndexTest, CanonicalizeSystemHeader) { + FileIndex M; + std::string File = testPath("bits/basic_string"); + M.update(File, build(File, "class string {};").getPointer()); + + FuzzyFindRequest Req; + Req.Query = ""; + M.fuzzyFind(Req, [&](const Symbol &Sym) { + EXPECT_EQ(Sym.Detail->IncludeHeader, ""); + }); +} +#endif + } // namespace } // namespace clangd } // namespace clang Index: unittests/clangd/HeadersTests.cpp =================================================================== --- /dev/null +++ unittests/clangd/HeadersTests.cpp @@ -0,0 +1,94 @@ +//===-- HeadersTests.cpp - Include headers unit tests -----------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "Headers.h" +#include "TestFS.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace clang { +namespace clangd { +namespace { + +class HeadersTest : public ::testing::Test { +public: + HeadersTest() { + CDB.ExtraClangFlags = {SearchDirArg.c_str()}; + FS.Files[MainFile] = ""; + } + +protected: + // Calculates the include path for \p Header, or returns "" on error. + std::string calculate(PathRef Header) { + auto VFS = FS.getTaggedFileSystem(MainFile).Value; + auto Cmd = CDB.getCompileCommand(MainFile); + assert(static_cast(Cmd)); + VFS->setCurrentWorkingDirectory(Cmd->Directory); + auto Path = + calculateIncludePath(MainFile, FS.Files[MainFile], Header, *Cmd, VFS); + if (!Path) { + llvm::consumeError(Path.takeError()); + return std::string(); + } + return std::move(*Path); + } + MockFSProvider FS; + MockCompilationDatabase CDB; + std::string MainFile = testPath("main.cpp"); + std::string Subdir = testPath("sub"); + std::string SearchDirArg = (llvm::Twine("-I") + Subdir).str(); +}; + +TEST_F(HeadersTest, InsertInclude) { + std::string Path = testPath("sub/bar.h"); + FS.Files[Path] = ""; + EXPECT_EQ(calculate(Path), "\"bar.h\""); +} + +TEST_F(HeadersTest, DontInsertDuplicateSameName) { + FS.Files[MainFile] = R"cpp( +#include "bar.h" +)cpp"; + std::string BarHeader = testPath("sub/bar.h"); + FS.Files[BarHeader] = ""; + EXPECT_EQ(calculate(BarHeader), ""); +} + +TEST_F(HeadersTest, DontInsertDuplicateDifferentName) { + std::string BarHeader = testPath("sub/bar.h"); + FS.Files[BarHeader] = ""; + FS.Files[MainFile] = R"cpp( +#include "sub/bar.h" // not shortest +)cpp"; + EXPECT_EQ(calculate(BarHeader), ""); +} + +TEST_F(HeadersTest, StillInsertIfTrasitivelyIncluded) { + std::string BazHeader = testPath("sub/baz.h"); + FS.Files[BazHeader] = ""; + std::string BarHeader = testPath("sub/bar.h"); + FS.Files[BarHeader] = R"cpp( +#include "baz.h" +)cpp"; + FS.Files[MainFile] = R"cpp( +#include "bar.h" +)cpp"; + EXPECT_EQ(calculate(BazHeader), "\"baz.h\""); +} + +TEST_F(HeadersTest, DoNotInsertIfInSameFile) { + MainFile = testPath("main.h"); + FS.Files[MainFile] = ""; + EXPECT_EQ(calculate(MainFile), ""); +} + +} // namespace +} // namespace clangd +} // namespace clang + Index: unittests/clangd/IndexTests.cpp =================================================================== --- unittests/clangd/IndexTests.cpp +++ unittests/clangd/IndexTests.cpp @@ -90,12 +90,15 @@ } std::vector match(const SymbolIndex &I, - const FuzzyFindRequest &Req) { + const FuzzyFindRequest &Req, + bool *Incomplete = nullptr) { std::vector Matches; - I.fuzzyFind(Req, [&](const Symbol &Sym) { + bool IsIncomplete = I.fuzzyFind(Req, [&](const Symbol &Sym) { Matches.push_back( (Sym.Scope + (Sym.Scope.empty() ? "" : "::") + Sym.Name).str()); }); + if (Incomplete) + *Incomplete = IsIncomplete; return Matches; } @@ -144,8 +147,10 @@ FuzzyFindRequest Req; Req.Query = "5"; Req.MaxCandidateCount = 3; - auto Matches = match(I, Req); + bool Incomplete; + auto Matches = match(I, Req, &Incomplete); EXPECT_EQ(Matches.size(), Req.MaxCandidateCount); + EXPECT_TRUE(Incomplete); } TEST(MemIndexTest, FuzzyMatch) { Index: unittests/clangd/SymbolCollectorTests.cpp =================================================================== --- unittests/clangd/SymbolCollectorTests.cpp +++ unittests/clangd/SymbolCollectorTests.cpp @@ -47,6 +47,9 @@ } MATCHER_P(QName, Name, "") { return (arg.Scope + arg.Name).str() == Name; } MATCHER_P(DeclURI, P, "") { return arg.CanonicalDeclaration.FileURI == P; } +MATCHER_P(IncludeHeader, P, "") { + return arg.Detail && arg.Detail->IncludeHeader == P; +} MATCHER_P(DeclRange, Offsets, "") { return arg.CanonicalDeclaration.StartOffset == Offsets.first && arg.CanonicalDeclaration.EndOffset == Offsets.second; @@ -62,41 +65,62 @@ namespace { class SymbolIndexActionFactory : public tooling::FrontendActionFactory { public: - SymbolIndexActionFactory(SymbolCollector::Options COpts) - : COpts(std::move(COpts)) {} + SymbolIndexActionFactory(SymbolCollector::Options COpts, + CommentHandler *PragmaHandler) + : COpts(std::move(COpts)), PragmaHandler(PragmaHandler) {} clang::FrontendAction *create() override { + class WrappedIndexAction : public WrapperFrontendAction { + public: + WrappedIndexAction(std::shared_ptr C, + const index::IndexingOptions &Opts, + CommentHandler *PragmaHandler) + : WrapperFrontendAction( + index::createIndexingAction(C, Opts, nullptr)), + PragmaHandler(PragmaHandler) {} + + std::unique_ptr + CreateASTConsumer(CompilerInstance &CI, StringRef InFile) override { + if (PragmaHandler) + CI.getPreprocessor().addCommentHandler(PragmaHandler); + return WrapperFrontendAction::CreateASTConsumer(CI, InFile); + } + + private: + index::IndexingOptions IndexOpts; + CommentHandler *PragmaHandler; + }; index::IndexingOptions IndexOpts; IndexOpts.SystemSymbolFilter = index::IndexingOptions::SystemSymbolFilterKind::All; IndexOpts.IndexFunctionLocals = false; Collector = std::make_shared(COpts); - FrontendAction *Action = - index::createIndexingAction(Collector, IndexOpts, nullptr).release(); - return Action; + return new WrappedIndexAction(Collector, std::move(IndexOpts), + PragmaHandler); } std::shared_ptr Collector; SymbolCollector::Options COpts; + CommentHandler *PragmaHandler; }; class SymbolCollectorTest : public ::testing::Test { public: SymbolCollectorTest() - : TestHeaderName(getVirtualTestFilePath("symbol.h").str()), - TestFileName(getVirtualTestFilePath("symbol.cc").str()) { + : InMemoryFileSystem(new vfs::InMemoryFileSystem), + TestHeaderName(testPath("symbol.h")), + TestFileName(testPath("symbol.cc")) { TestHeaderURI = URI::createFile(TestHeaderName).toString(); TestFileURI = URI::createFile(TestFileName).toString(); } bool runSymbolCollector(StringRef HeaderCode, StringRef MainCode, const std::vector &ExtraArgs = {}) { - llvm::IntrusiveRefCntPtr InMemoryFileSystem( - new vfs::InMemoryFileSystem); llvm::IntrusiveRefCntPtr Files( new FileManager(FileSystemOptions(), InMemoryFileSystem)); - auto Factory = llvm::make_unique(CollectorOpts); + auto Factory = llvm::make_unique( + CollectorOpts, PragmaHandler.get()); std::vector Args = {"symbol_collector", "-fsyntax-only", "-std=c++11", "-include", @@ -117,12 +141,14 @@ } protected: + llvm::IntrusiveRefCntPtr InMemoryFileSystem; std::string TestHeaderName; std::string TestHeaderURI; std::string TestFileName; std::string TestFileURI; SymbolSlab Symbols; SymbolCollector::Options CollectorOpts; + std::unique_ptr PragmaHandler; }; TEST_F(SymbolCollectorTest, CollectSymbols) { @@ -218,9 +244,8 @@ CollectorOpts.IndexMainFiles = false; TestHeaderName = "x.h"; TestFileName = "x.cpp"; - TestHeaderURI = - URI::createFile(getVirtualTestFilePath(TestHeaderName)).toString(); - CollectorOpts.FallbackDir = getVirtualTestRoot(); + TestHeaderURI = URI::createFile(testPath(TestHeaderName)).toString(); + CollectorOpts.FallbackDir = testRoot(); runSymbolCollector("class Foo {};", /*Main=*/""); EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf(QName("Foo"), DeclURI(TestHeaderURI)))); @@ -231,8 +256,8 @@ CollectorOpts.IndexMainFiles = false; // Use test URI scheme from URITests.cpp CollectorOpts.URISchemes.insert(CollectorOpts.URISchemes.begin(), "unittest"); - TestHeaderName = getVirtualTestFilePath("test-root/x.h").str(); - TestFileName = getVirtualTestFilePath("test-root/x.cpp").str(); + TestHeaderName = testPath("test-root/x.h"); + TestFileName = testPath("test-root/x.cpp"); runSymbolCollector("class Foo {};", /*Main=*/""); EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf(QName("Foo"), DeclURI("unittest:x.h")))); @@ -555,6 +580,46 @@ QName("clang::Foo2"))); } +TEST_F(SymbolCollectorTest, IncludeHeaderSameAsFileURI) { + CollectorOpts.CollectIncludePath = true; + runSymbolCollector("class Foo {};", /*Main=*/""); + EXPECT_THAT(Symbols, + UnorderedElementsAre(AllOf(QName("Foo"), DeclURI(TestHeaderURI), + IncludeHeader("")))); +} + +#ifndef LLVM_ON_WIN32 +TEST_F(SymbolCollectorTest, CanonicalSTLHeader) { + CollectorOpts.CollectIncludePath = true; + CanonicalIncludes Includes; + addSystemHeadersMapping(&Includes); + CollectorOpts.Includes = &Includes; + // bits/basic_string.h$ should be mapped to + TestHeaderName = "/nasty/bits/basic_string.h"; + TestFileName = "/nasty/bits/basic_string.cpp"; + TestHeaderURI = URI::createFile(TestHeaderName).toString(); + runSymbolCollector("class string {};", /*Main=*/""); + EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf(QName("string"), + DeclURI(TestHeaderURI), + IncludeHeader("")))); +} +#endif + +TEST_F(SymbolCollectorTest, IWYUPragma) { + CollectorOpts.CollectIncludePath = true; + CanonicalIncludes Includes; + PragmaHandler = collectIWYUHeaderMaps(&Includes); + CollectorOpts.Includes = &Includes; + const std::string Header = R"( + // IWYU pragma: private, include the/good/header.h + class Foo {}; + )"; + runSymbolCollector(Header, /*Main=*/""); + EXPECT_THAT(Symbols, UnorderedElementsAre( + AllOf(QName("Foo"), DeclURI(TestHeaderURI), + IncludeHeader("\"the/good/header.h\"")))); +} + } // namespace } // namespace clangd } // namespace clang Index: unittests/clangd/SyncAPI.h =================================================================== --- unittests/clangd/SyncAPI.h +++ unittests/clangd/SyncAPI.h @@ -22,6 +22,22 @@ runCodeComplete(ClangdServer &Server, PathRef File, Position Pos, clangd::CodeCompleteOptions Opts, llvm::Optional OverridenContents = llvm::None); + +llvm::Expected> +runSignatureHelp(ClangdServer &Server, PathRef File, Position Pos, + llvm::Optional OverridenContents = llvm::None); + +llvm::Expected>> +runFindDefinitions(ClangdServer &Server, PathRef File, Position Pos); + +llvm::Expected>> +runFindDocumentHighlights(ClangdServer &Server, PathRef File, Position Pos); + +llvm::Expected> +runRename(ClangdServer &Server, PathRef File, Position Pos, StringRef NewName); + +std::string runDumpAST(ClangdServer &Server, PathRef File); + } // namespace clangd } // namespace clang Index: unittests/clangd/SyncAPI.cpp =================================================================== --- unittests/clangd/SyncAPI.cpp +++ unittests/clangd/SyncAPI.cpp @@ -17,7 +17,9 @@ /// T Result; /// someAsyncFunc(Param1, Param2, /*Callback=*/capture(Result)); template struct CaptureProxy { - CaptureProxy(T &Target) : Target(&Target) {} + CaptureProxy(llvm::Optional &Target) : Target(&Target) { + assert(!Target.hasValue()); + } CaptureProxy(const CaptureProxy &) = delete; CaptureProxy &operator=(const CaptureProxy &) = delete; @@ -30,25 +32,31 @@ operator UniqueFunction() && { assert(!Future.valid() && "conversion to callback called multiple times"); Future = Promise.get_future(); - return BindWithForward([](std::promise Promise, - T Value) { Promise.set_value(std::move(Value)); }, - std::move(Promise)); + return BindWithForward( + [](std::promise> Promise, T Value) { + Promise.set_value(std::make_shared(std::move(Value))); + }, + std::move(Promise)); } ~CaptureProxy() { if (!Target) return; assert(Future.valid() && "conversion to callback was not called"); - *Target = Future.get(); + assert(!Target->hasValue()); + Target->emplace(std::move(*Future.get())); } private: - T *Target; - std::promise Promise; - std::future Future; + llvm::Optional *Target; + // Using shared_ptr to workaround compilation errors with MSVC. + // MSVC only allows default-construcitble and copyable objects as future<> + // arguments. + std::promise> Promise; + std::future> Future; }; -template CaptureProxy capture(T &Target) { +template CaptureProxy capture(llvm::Optional &Target) { return CaptureProxy(Target); } } // namespace @@ -57,9 +65,44 @@ runCodeComplete(ClangdServer &Server, PathRef File, Position Pos, clangd::CodeCompleteOptions Opts, llvm::Optional OverridenContents) { - Tagged Result; + llvm::Optional> Result; Server.codeComplete(File, Pos, Opts, capture(Result), OverridenContents); - return Result; + return std::move(*Result); +} + +llvm::Expected> +runSignatureHelp(ClangdServer &Server, PathRef File, Position Pos, + llvm::Optional OverridenContents) { + llvm::Optional>> Result; + Server.signatureHelp(File, Pos, capture(Result), OverridenContents); + return std::move(*Result); +} + +llvm::Expected>> +runFindDefinitions(ClangdServer &Server, PathRef File, Position Pos) { + llvm::Optional>>> Result; + Server.findDefinitions(File, Pos, capture(Result)); + return std::move(*Result); +} + +llvm::Expected>> +runFindDocumentHighlights(ClangdServer &Server, PathRef File, Position Pos) { + llvm::Optional>>> Result; + Server.findDocumentHighlights(File, Pos, capture(Result)); + return std::move(*Result); +} + +llvm::Expected> +runRename(ClangdServer &Server, PathRef File, Position Pos, StringRef NewName) { + llvm::Optional>> Result; + Server.rename(File, Pos, NewName, capture(Result)); + return std::move(*Result); +} + +std::string runDumpAST(ClangdServer &Server, PathRef File) { + llvm::Optional Result; + Server.dumpAST(File, capture(Result)); + return std::move(*Result); } } // namespace clangd Index: unittests/clangd/TUSchedulerTests.cpp =================================================================== --- unittests/clangd/TUSchedulerTests.cpp +++ unittests/clangd/TUSchedulerTests.cpp @@ -33,12 +33,9 @@ std::move(Contents)}; } - void changeFile(PathRef File, std::string Contents) { - Files[File] = Contents; - } + llvm::StringMap Files; private: - llvm::StringMap Files; MockCompilationDatabase CDB; }; @@ -47,41 +44,43 @@ /*StorePreamblesInMemory=*/true, /*ASTParsedCallback=*/nullptr); - auto Added = getVirtualTestFilePath("added.cpp"); - changeFile(Added, ""); + auto Added = testPath("added.cpp"); + Files[Added] = ""; - auto Missing = getVirtualTestFilePath("missing.cpp"); - changeFile(Missing, ""); + auto Missing = testPath("missing.cpp"); + Files[Missing] = ""; S.update(Added, getInputs(Added, ""), ignoreUpdate); // Assert each operation for missing file is an error (even if it's available // in VFS). - S.runWithAST(Missing, [&](llvm::Expected AST) { + S.runWithAST("", Missing, [&](llvm::Expected AST) { ASSERT_FALSE(bool(AST)); ignoreError(AST.takeError()); }); - S.runWithPreamble(Missing, [&](llvm::Expected Preamble) { - ASSERT_FALSE(bool(Preamble)); - ignoreError(Preamble.takeError()); - }); + S.runWithPreamble("", Missing, + [&](llvm::Expected Preamble) { + ASSERT_FALSE(bool(Preamble)); + ignoreError(Preamble.takeError()); + }); // remove() shouldn't crash on missing files. S.remove(Missing); // Assert there aren't any errors for added file. - S.runWithAST( - Added, [&](llvm::Expected AST) { EXPECT_TRUE(bool(AST)); }); - S.runWithPreamble(Added, [&](llvm::Expected Preamble) { + S.runWithAST("", Added, [&](llvm::Expected AST) { + EXPECT_TRUE(bool(AST)); + }); + S.runWithPreamble("", Added, [&](llvm::Expected Preamble) { EXPECT_TRUE(bool(Preamble)); }); S.remove(Added); // Assert that all operations fail after removing the file. - S.runWithAST(Added, [&](llvm::Expected AST) { + S.runWithAST("", Added, [&](llvm::Expected AST) { ASSERT_FALSE(bool(AST)); ignoreError(AST.takeError()); }); - S.runWithPreamble(Added, [&](llvm::Expected Preamble) { + S.runWithPreamble("", Added, [&](llvm::Expected Preamble) { ASSERT_FALSE(bool(Preamble)); ignoreError(Preamble.takeError()); }); @@ -106,9 +105,9 @@ std::vector Files; for (int I = 0; I < FilesCount; ++I) { - Files.push_back( - getVirtualTestFilePath("foo" + std::to_string(I) + ".cpp").str()); - changeFile(Files.back(), ""); + std::string Name = "foo" + std::to_string(I) + ".cpp"; + Files.push_back(testPath(Name)); + this->Files[Files.back()] = ""; } llvm::StringRef Contents1 = R"cpp(int a;)cpp"; @@ -146,24 +145,27 @@ { WithContextValue WithNonce(NonceKey, ++Nonce); - S.runWithAST(File, [Inputs, Nonce, &Mut, &TotalASTReads]( - llvm::Expected AST) { - EXPECT_THAT(Context::current().get(NonceKey), Pointee(Nonce)); - - ASSERT_TRUE((bool)AST); - EXPECT_EQ(AST->Inputs.FS, Inputs.FS); - EXPECT_EQ(AST->Inputs.Contents, Inputs.Contents); - - std::lock_guard Lock(Mut); - ++TotalASTReads; - }); + S.runWithAST("CheckAST", File, + [Inputs, Nonce, &Mut, + &TotalASTReads](llvm::Expected AST) { + EXPECT_THAT(Context::current().get(NonceKey), + Pointee(Nonce)); + + ASSERT_TRUE((bool)AST); + EXPECT_EQ(AST->Inputs.FS, Inputs.FS); + EXPECT_EQ(AST->Inputs.Contents, Inputs.Contents); + + std::lock_guard Lock(Mut); + ++TotalASTReads; + }); } { WithContextValue WithNonce(NonceKey, ++Nonce); S.runWithPreamble( - File, [Inputs, Nonce, &Mut, &TotalPreambleReads]( - llvm::Expected Preamble) { + "CheckPreamble", File, + [Inputs, Nonce, &Mut, &TotalPreambleReads]( + llvm::Expected Preamble) { EXPECT_THAT(Context::current().get(NonceKey), Pointee(Nonce)); ASSERT_TRUE((bool)Preamble); Index: unittests/clangd/TestFS.h =================================================================== --- unittests/clangd/TestFS.h +++ unittests/clangd/TestFS.h @@ -29,7 +29,8 @@ Tagged> getTaggedFileSystem(PathRef File) override; - llvm::Optional> ExpectedFile; + llvm::Optional ExpectedFile; + // If relative paths are used, they are resolved with testPath(). llvm::StringMap Files; VFSTag Tag = VFSTag(); }; @@ -49,10 +50,10 @@ }; // Returns an absolute (fake) test directory for this OS. -const char *getVirtualTestRoot(); +const char *testRoot(); // Returns a suitable absolute path for this OS. -llvm::SmallString<32> getVirtualTestFilePath(PathRef File); +std::string testPath(PathRef File); } // namespace clangd } // namespace clang Index: unittests/clangd/TestFS.cpp =================================================================== --- unittests/clangd/TestFS.cpp +++ unittests/clangd/TestFS.cpp @@ -12,14 +12,17 @@ namespace clang { namespace clangd { +using namespace llvm; + IntrusiveRefCntPtr -buildTestFS(llvm::StringMap const &Files) { +buildTestFS(StringMap const &Files) { IntrusiveRefCntPtr MemFS( new vfs::InMemoryFileSystem); - for (auto &FileAndContents : Files) + for (auto &FileAndContents : Files) { MemFS->addFile(FileAndContents.first(), time_t(), - llvm::MemoryBuffer::getMemBuffer(FileAndContents.second, - FileAndContents.first())); + MemoryBuffer::getMemBuffer(FileAndContents.second, + FileAndContents.first())); + } return MemFS; } @@ -38,20 +41,20 @@ // -ffreestanding avoids implicit stdc-predef.h. } -llvm::Optional +Optional MockCompilationDatabase::getCompileCommand(PathRef File) const { if (ExtraClangFlags.empty()) - return llvm::None; + return None; auto CommandLine = ExtraClangFlags; - auto FileName = llvm::sys::path::filename(File); + auto FileName = sys::path::filename(File); CommandLine.insert(CommandLine.begin(), "clang"); CommandLine.insert(CommandLine.end(), UseRelPaths ? FileName : File); - return {tooling::CompileCommand(llvm::sys::path::parent_path(File), FileName, + return {tooling::CompileCommand(sys::path::parent_path(File), FileName, std::move(CommandLine), "")}; } -const char *getVirtualTestRoot() { +const char *testRoot() { #ifdef LLVM_ON_WIN32 return "C:\\clangd-test"; #else @@ -59,12 +62,12 @@ #endif } -llvm::SmallString<32> getVirtualTestFilePath(PathRef File) { - assert(llvm::sys::path::is_relative(File) && "FileName should be relative"); +std::string testPath(PathRef File) { + assert(sys::path::is_relative(File) && "FileName should be relative"); - llvm::SmallString<32> Path; - llvm::sys::path::append(Path, getVirtualTestRoot(), File); - return Path; + SmallString<32> Path; + sys::path::append(Path, testRoot(), File); + return Path.str(); } } // namespace clangd Index: unittests/clangd/ThreadingTests.cpp =================================================================== --- unittests/clangd/ThreadingTests.cpp +++ unittests/clangd/ThreadingTests.cpp @@ -29,7 +29,7 @@ AsyncTaskRunner Tasks; auto scheduleIncrements = [&]() { for (int TaskI = 0; TaskI < TasksCnt; ++TaskI) { - Tasks.runAsync([&Counter, &Mutex, IncrementsPerTask]() { + Tasks.runAsync("task", [&Counter, &Mutex, IncrementsPerTask]() { for (int Increment = 0; Increment < IncrementsPerTask; ++Increment) { std::lock_guard Lock(Mutex); ++Counter; Index: unittests/clangd/TraceTests.cpp =================================================================== --- unittests/clangd/TraceTests.cpp +++ unittests/clangd/TraceTests.cpp @@ -114,12 +114,10 @@ ASSERT_NE(++Event, Events->end()) << "Expected thread name"; EXPECT_TRUE(VerifyObject(*Event, {{"ph", "M"}, {"name", "thread_name"}})); } - ASSERT_NE(++Event, Events->end()) << "Expected span start"; - EXPECT_TRUE(VerifyObject(*Event, {{"ph", "B"}, {"name", "A"}})); ASSERT_NE(++Event, Events->end()) << "Expected log message"; EXPECT_TRUE(VerifyObject(*Event, {{"ph", "i"}, {"name", "Log"}})); ASSERT_NE(++Event, Events->end()) << "Expected span end"; - EXPECT_TRUE(VerifyObject(*Event, {{"ph", "E"}})); + EXPECT_TRUE(VerifyObject(*Event, {{"ph", "X"}, {"name", "A"}})); ASSERT_EQ(++Event, Events->end()); ASSERT_EQ(++Prop, Root->end()); } Index: unittests/clangd/URITests.cpp =================================================================== --- unittests/clangd/URITests.cpp +++ unittests/clangd/URITests.cpp @@ -176,10 +176,10 @@ } TEST(URITest, Platform) { - auto Path = getVirtualTestFilePath("x"); + auto Path = testPath("x"); auto U = URI::create(Path, "file"); EXPECT_TRUE(static_cast(U)); - EXPECT_THAT(resolveOrDie(*U), Path.str()); + EXPECT_THAT(resolveOrDie(*U), Path); } TEST(URITest, ResolveFailed) { Index: unittests/clangd/XRefsTests.cpp =================================================================== --- unittests/clangd/XRefsTests.cpp +++ unittests/clangd/XRefsTests.cpp @@ -10,6 +10,7 @@ #include "ClangdUnit.h" #include "Compiler.h" #include "Matchers.h" +#include "SyncAPI.h" #include "TestFS.h" #include "XRefs.h" #include "clang/Frontend/CompilerInvocation.h" @@ -45,9 +46,8 @@ // FIXME: this is duplicated with FileIndexTests. Share it. ParsedAST build(StringRef Code) { - auto TestFile = getVirtualTestFilePath("Foo.cpp"); - auto CI = - createInvocationFromCommandLine({"clang", "-xc++", TestFile.c_str()}); + auto CI = createInvocationFromCommandLine( + {"clang", "-xc++", testPath("Foo.cpp").c_str()}); auto Buf = MemoryBuffer::getMemBuffer(Code); auto AST = ParsedAST::Build(std::move(CI), nullptr, std::move(Buf), std::make_shared(), @@ -245,16 +245,322 @@ ClangdServer Server(CDB, DiagConsumer, FS, /*AsyncThreadsCount=*/0, /*StorePreambleInMemory=*/true); - auto FooCpp = getVirtualTestFilePath("foo.cpp"); + auto FooCpp = testPath("foo.cpp"); FS.Files[FooCpp] = ""; Server.addDocument(FooCpp, SourceAnnotations.code()); - auto Locations = Server.findDefinitions(FooCpp, SourceAnnotations.point()); + auto Locations = + runFindDefinitions(Server, FooCpp, SourceAnnotations.point()); EXPECT_TRUE(bool(Locations)) << "findDefinitions returned an error"; - EXPECT_THAT(Locations->Value, - ElementsAre(Location{URIForFile{FooCpp.str()}, - SourceAnnotations.range()})); + EXPECT_THAT( + Locations->Value, + ElementsAre(Location{URIForFile{FooCpp}, SourceAnnotations.range()})); +} + +TEST(Hover, All) { + struct OneTest { + StringRef Input; + StringRef ExpectedHover; + }; + + OneTest Tests[] = { + { + R"cpp(// Local variable + int main() { + int bonjour; + ^bonjour = 2; + int test1 = bonjour; + } + )cpp", + "Declared in function main\n\nint bonjour", + }, + { + R"cpp(// Local variable in method + struct s { + void method() { + int bonjour; + ^bonjour = 2; + } + }; + )cpp", + "Declared in function s::method\n\nint bonjour", + }, + { + R"cpp(// Struct + namespace ns1 { + struct MyClass {}; + } // namespace ns1 + int main() { + ns1::My^Class* Params; + } + )cpp", + "Declared in namespace ns1\n\nstruct MyClass {}", + }, + { + R"cpp(// Class + namespace ns1 { + class MyClass {}; + } // namespace ns1 + int main() { + ns1::My^Class* Params; + } + )cpp", + "Declared in namespace ns1\n\nclass MyClass {}", + }, + { + R"cpp(// Union + namespace ns1 { + union MyUnion { int x; int y; }; + } // namespace ns1 + int main() { + ns1::My^Union Params; + } + )cpp", + "Declared in namespace ns1\n\nunion MyUnion {}", + }, + { + R"cpp(// Function definition via pointer + int foo(int) {} + int main() { + auto *X = &^foo; + } + )cpp", + "Declared in global namespace\n\nint foo(int)", + }, + { + R"cpp(// Function declaration via call + int foo(int); + int main() { + return ^foo(42); + } + )cpp", + "Declared in global namespace\n\nint foo(int)", + }, + { + R"cpp(// Field + struct Foo { int x; }; + int main() { + Foo bar; + bar.^x; + } + )cpp", + "Declared in struct Foo\n\nint x", + }, + { + R"cpp(// Field with initialization + struct Foo { int x = 5; }; + int main() { + Foo bar; + bar.^x; + } + )cpp", + "Declared in struct Foo\n\nint x = 5", + }, + { + R"cpp(// Static field + struct Foo { static int x; }; + int main() { + Foo::^x; + } + )cpp", + "Declared in struct Foo\n\nstatic int x", + }, + { + R"cpp(// Field, member initializer + struct Foo { + int x; + Foo() : ^x(0) {} + }; + )cpp", + "Declared in struct Foo\n\nint x", + }, + { + R"cpp(// Field, GNU old-style field designator + struct Foo { int x; }; + int main() { + Foo bar = { ^x : 1 }; + } + )cpp", + "Declared in struct Foo\n\nint x", + }, + { + R"cpp(// Field, field designator + struct Foo { int x; }; + int main() { + Foo bar = { .^x = 2 }; + } + )cpp", + "Declared in struct Foo\n\nint x", + }, + { + R"cpp(// Method call + struct Foo { int x(); }; + int main() { + Foo bar; + bar.^x(); + } + )cpp", + "Declared in struct Foo\n\nint x()", + }, + { + R"cpp(// Static method call + struct Foo { static int x(); }; + int main() { + Foo::^x(); + } + )cpp", + "Declared in struct Foo\n\nstatic int x()", + }, + { + R"cpp(// Typedef + typedef int Foo; + int main() { + ^Foo bar; + } + )cpp", + "Declared in global namespace\n\ntypedef int Foo", + }, + { + R"cpp(// Namespace + namespace ns { + struct Foo { static void bar(); } + } // namespace ns + int main() { ^ns::Foo::bar(); } + )cpp", + "Declared in global namespace\n\nnamespace ns {\n}", + }, + { + R"cpp(// Anonymous namespace + namespace ns { + namespace { + int foo; + } // anonymous namespace + } // namespace ns + int main() { ns::f^oo++; } + )cpp", + "Declared in namespace ns::(anonymous)\n\nint foo", + }, + { + R"cpp(// Macro + #define MACRO 0 + #define MACRO 1 + int main() { return ^MACRO; } + #define MACRO 2 + #undef macro + )cpp", + "#define MACRO", + }, + { + R"cpp(// Forward class declaration + class Foo; + class Foo {}; + F^oo* foo(); + )cpp", + "Declared in global namespace\n\nclass Foo {}", + }, + { + R"cpp(// Function declaration + void foo(); + void g() { f^oo(); } + void foo() {} + )cpp", + "Declared in global namespace\n\nvoid foo()", + }, + { + R"cpp(// Enum declaration + enum Hello { + ONE, TWO, THREE, + }; + void foo() { + Hel^lo hello = ONE; + } + )cpp", + "Declared in global namespace\n\nenum Hello {\n}", + }, + { + R"cpp(// Enumerator + enum Hello { + ONE, TWO, THREE, + }; + void foo() { + Hello hello = O^NE; + } + )cpp", + "Declared in enum Hello\n\nONE", + }, + { + R"cpp(// Enumerator in anonymous enum + enum { + ONE, TWO, THREE, + }; + void foo() { + int hello = O^NE; + } + )cpp", + "Declared in enum (anonymous)\n\nONE", + }, + { + R"cpp(// Global variable + static int hey = 10; + void foo() { + he^y++; + } + )cpp", + "Declared in global namespace\n\nstatic int hey = 10", + }, + { + R"cpp(// Global variable in namespace + namespace ns1 { + static int hey = 10; + } + void foo() { + ns1::he^y++; + } + )cpp", + "Declared in namespace ns1\n\nstatic int hey = 10", + }, + { + R"cpp(// Field in anonymous struct + static struct { + int hello; + } s; + void foo() { + s.he^llo++; + } + )cpp", + "Declared in struct (anonymous)\n\nint hello", + }, + { + R"cpp(// Templated function + template + T foo() { + return 17; + } + void g() { auto x = f^oo(); } + )cpp", + "Declared in global namespace\n\ntemplate T foo()", + }, + { + R"cpp(// Anonymous union + struct outer { + union { + int abc, def; + } v; + }; + void g() { struct outer o; o.v.d^ef++; } + )cpp", + "Declared in union outer::(anonymous)\n\nint def", + }, + }; + + for (const OneTest &Test : Tests) { + Annotations T(Test.Input); + auto AST = build(T.code()); + Hover H = getHover(AST, T.point()); + + EXPECT_EQ(H.contents.value, Test.ExpectedHover) << Test.Input; + } } } // namespace