diff --git a/.clang-tidy b/.clang-tidy index 3f2f2c054eb3d..1d4438dbfda0c 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -16,4 +16,5 @@ CheckOptions: value: CamelCase - key: readability-identifier-naming.IgnoreMainLikeFunctions value: 1 - + - key: readability-redundant-member-init.IgnoreBaseInCopyConstructors + value: 1 diff --git a/.github/workflows/issue-subscriber.yml b/.github/workflows/issue-subscriber.yml new file mode 100644 index 0000000000000..51c55fa362add --- /dev/null +++ b/.github/workflows/issue-subscriber.yml @@ -0,0 +1,35 @@ +name: Issue Subscriber + +on: + issues: + types: + - labeled + +jobs: + auto-subscribe: + runs-on: ubuntu-latest + if: github.repository == 'llvm/llvm-project' + steps: + - name: Update watchers + uses: actions/github-script@v5 + with: + github-token: ${{ secrets.ISSUE_MENTION_SECRET }} + script: | + const teamname = "issue-subscribers-" + context.payload.label.name.replace(/ /g, "-").replace(":","-").replace("/","-"); + const comment = "@llvm/" + teamname; + try { + // This will throw an exception if the team does not exist and no + // comment will be created. + team = await github.rest.teams.getByName({ + org: context.repo.owner, + team_slug: teamname + }); + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: comment + }); + } catch (e){ + console.log(e); + } diff --git a/clang-tools-extra/clang-doc/Mapper.cpp b/clang-tools-extra/clang-doc/Mapper.cpp index 790f11bb69c5b..16a52e843fccc 100644 --- a/clang-tools-extra/clang-doc/Mapper.cpp +++ b/clang-tools-extra/clang-doc/Mapper.cpp @@ -14,8 +14,6 @@ #include "llvm/ADT/StringExtras.h" #include "llvm/Support/Error.h" -using clang::comments::FullComment; - namespace clang { namespace doc { @@ -68,7 +66,7 @@ bool MapASTVisitor::VisitCXXMethodDecl(const CXXMethodDecl *D) { bool MapASTVisitor::VisitFunctionDecl(const FunctionDecl *D) { // Don't visit CXXMethodDecls twice - if (dyn_cast(D)) + if (isa(D)) return true; return mapDecl(D); } diff --git a/clang-tools-extra/clang-doc/Serialize.cpp b/clang-tools-extra/clang-doc/Serialize.cpp index e132c56cb0001..29762b6b54b1e 100644 --- a/clang-tools-extra/clang-doc/Serialize.cpp +++ b/clang-tools-extra/clang-doc/Serialize.cpp @@ -382,7 +382,7 @@ populateParentNamespaces(llvm::SmallVector &Namespaces, // corresponds to a Record and if it doesn't have any namespace (because this // means it's in the global namespace). Also if its outermost namespace is a // record because that record matches the previous condition mentioned. - if ((Namespaces.empty() && dyn_cast(D)) || + if ((Namespaces.empty() && isa(D)) || (!Namespaces.empty() && Namespaces.back().RefType == InfoType::IT_record)) Namespaces.emplace_back(SymbolID(), "GlobalNamespace", InfoType::IT_namespace); @@ -419,10 +419,10 @@ static void populateFunctionInfo(FunctionInfo &I, const FunctionDecl *D, populateSymbolInfo(I, D, FC, LineNumber, Filename, IsFileInRootDir, IsInAnonymousNamespace); if (const auto *T = getDeclForType(D->getReturnType())) { - if (dyn_cast(T)) + if (isa(T)) I.ReturnType = TypeInfo(getUSRForDecl(T), T->getNameAsString(), InfoType::IT_enum, getInfoRelativePath(T)); - else if (dyn_cast(T)) + else if (isa(T)) I.ReturnType = TypeInfo(getUSRForDecl(T), T->getNameAsString(), InfoType::IT_record, getInfoRelativePath(T)); } else { diff --git a/clang-tools-extra/clang-include-fixer/YamlSymbolIndex.cpp b/clang-tools-extra/clang-include-fixer/YamlSymbolIndex.cpp index de72e9a9b9324..4271d9aa4e677 100644 --- a/clang-tools-extra/clang-include-fixer/YamlSymbolIndex.cpp +++ b/clang-tools-extra/clang-include-fixer/YamlSymbolIndex.cpp @@ -15,7 +15,6 @@ #include #include -using clang::find_all_symbols::SymbolInfo; using clang::find_all_symbols::SymbolAndSignals; namespace clang { diff --git a/clang-tools-extra/clang-include-fixer/find-all-symbols/SymbolInfo.cpp b/clang-tools-extra/clang-include-fixer/find-all-symbols/SymbolInfo.cpp index e5b4dba4b7ad7..4a5f8353b4105 100644 --- a/clang-tools-extra/clang-include-fixer/find-all-symbols/SymbolInfo.cpp +++ b/clang-tools-extra/clang-include-fixer/find-all-symbols/SymbolInfo.cpp @@ -13,8 +13,6 @@ #include "llvm/Support/raw_ostream.h" using llvm::yaml::MappingTraits; -using llvm::yaml::IO; -using llvm::yaml::Input; using ContextType = clang::find_all_symbols::SymbolInfo::ContextType; using clang::find_all_symbols::SymbolInfo; using clang::find_all_symbols::SymbolAndSignals; diff --git a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp index 6c5054fdca28e..a76fe16effb6a 100644 --- a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp +++ b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp @@ -719,7 +719,7 @@ void ClangTidyDiagnosticConsumer::checkFilters(SourceLocation Location, } if (!*Context.getOptions().SystemHeaders && - Sources.isInSystemHeader(Location)) + (Sources.isInSystemHeader(Location) || Sources.isInSystemMacro(Location))) return; // FIXME: We start with a conservative approach here, but the actual type of diff --git a/clang-tools-extra/clang-tidy/abseil/DurationFactoryScaleCheck.cpp b/clang-tools-extra/clang-tidy/abseil/DurationFactoryScaleCheck.cpp index aa839beddac6e..c9f3a7db03461 100644 --- a/clang-tools-extra/clang-tidy/abseil/DurationFactoryScaleCheck.cpp +++ b/clang-tools-extra/clang-tidy/abseil/DurationFactoryScaleCheck.cpp @@ -221,7 +221,6 @@ void DurationFactoryScaleCheck::check(const MatchFinder::MatchResult &Result) { tooling::fixit::getText(*Remainder, *Result.Context) + ")") .str()); } - return; } } // namespace abseil diff --git a/clang-tools-extra/clang-tidy/abseil/DurationRewriter.h b/clang-tools-extra/clang-tidy/abseil/DurationRewriter.h index 135d15b635cdb..0803a7a4c0e02 100644 --- a/clang-tools-extra/clang-tidy/abseil/DurationRewriter.h +++ b/clang-tools-extra/clang-tidy/abseil/DurationRewriter.h @@ -138,4 +138,4 @@ AST_MATCHER_FUNCTION_P(ast_matchers::internal::Matcher, } // namespace tidy } // namespace clang -#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_DURATIONCOMPARISONCHECK_H +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_DURATIONREWRITER_H diff --git a/clang-tools-extra/clang-tidy/add_new_check.py b/clang-tools-extra/clang-tidy/add_new_check.py index a3554b0959759..1e26b07121c61 100755 --- a/clang-tools-extra/clang-tidy/add_new_check.py +++ b/clang-tools-extra/clang-tidy/add_new_check.py @@ -324,16 +324,20 @@ def has_auto_fix(check_name): dirname, _, check_name = check_name.partition("-") checker_code = get_actual_filename(os.path.join(clang_tidy_path, dirname), - get_camel_name(check_name) + '.cpp') - + get_camel_check_name(check_name) + '.cpp') if not os.path.isfile(checker_code): - return "" + # Some older checks don't end with 'Check.cpp' + checker_code = get_actual_filename(os.path.join(clang_tidy_path, dirname), + get_camel_name(check_name) + '.cpp') + if not os.path.isfile(checker_code): + return '' with io.open(checker_code, encoding='utf8') as f: code = f.read() - if 'FixItHint' in code or "ReplacementText" in code or "fixit" in code: - # Some simple heuristics to figure out if a checker has an autofix or not. - return ' "Yes"' + for needle in ['FixItHint', 'ReplacementText', 'fixit', 'TransformerClangTidyCheck']: + if needle in code: + # Some simple heuristics to figure out if a checker has an autofix or not. + return ' "Yes"' return "" def process_doc(doc_file): @@ -416,7 +420,11 @@ def write_docs(module_path, module, check_name): def get_camel_name(check_name): return ''.join(map(lambda elem: elem.capitalize(), - check_name.split('-'))) + 'Check' + check_name.split('-'))) + + +def get_camel_check_name(check_name): + return get_camel_name(check_name) + 'Check' def main(): @@ -458,7 +466,7 @@ def main(): module = args.module check_name = args.check - check_name_camel = get_camel_name(check_name) + check_name_camel = get_camel_check_name(check_name) if check_name.startswith(module): print('Check name "%s" must not start with the module "%s". Exiting.' % ( check_name, module)) diff --git a/clang-tools-extra/clang-tidy/android/CloexecCheck.cpp b/clang-tools-extra/clang-tidy/android/CloexecCheck.cpp index 64c8797934d23..d373877713f18 100644 --- a/clang-tools-extra/clang-tidy/android/CloexecCheck.cpp +++ b/clang-tools-extra/clang-tidy/android/CloexecCheck.cpp @@ -87,7 +87,7 @@ void CloexecCheck::insertStringFlag( // Check if the may be in the mode string. const auto *ModeStr = dyn_cast(ModeArg->IgnoreParenCasts()); - if (!ModeStr || (ModeStr->getString().find(Mode) != StringRef::npos)) + if (!ModeStr || ModeStr->getString().contains(Mode)) return; std::string ReplacementText = buildFixMsgForStringFlag( diff --git a/clang-tools-extra/clang-tidy/bugprone/NotNullTerminatedResultCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/NotNullTerminatedResultCheck.cpp index 36d83b2a3ea31..200528b1c061d 100644 --- a/clang-tools-extra/clang-tidy/bugprone/NotNullTerminatedResultCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/NotNullTerminatedResultCheck.cpp @@ -144,7 +144,7 @@ static StringRef exprToStr(const Expr *E, return Lexer::getSourceText( CharSourceRange::getTokenRange(E->getSourceRange()), - *Result.SourceManager, Result.Context->getLangOpts(), 0); + *Result.SourceManager, Result.Context->getLangOpts(), nullptr); } // Returns the proper token based end location of \p E. @@ -477,7 +477,7 @@ static void insertNullTerminatorExpr(StringRef Name, FunctionExpr->getBeginLoc()); StringRef SpaceBeforeStmtStr = Lexer::getSourceText( CharSourceRange::getCharRange(SpaceRange), *Result.SourceManager, - Result.Context->getLangOpts(), 0); + Result.Context->getLangOpts(), nullptr); SmallString<128> NewAddNullTermExprStr; NewAddNullTermExprStr = diff --git a/clang-tools-extra/clang-tidy/bugprone/ReservedIdentifierCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/ReservedIdentifierCheck.cpp index 8da0469554250..4bf841648f948 100644 --- a/clang-tools-extra/clang-tidy/bugprone/ReservedIdentifierCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/ReservedIdentifierCheck.cpp @@ -64,7 +64,7 @@ static std::string collapseConsecutive(StringRef Str, char C) { static bool hasReservedDoubleUnderscore(StringRef Name, const LangOptions &LangOpts) { if (LangOpts.CPlusPlus) - return Name.find("__") != StringRef::npos; + return Name.contains("__"); return Name.startswith("__"); } diff --git a/clang-tools-extra/clang-tidy/cert/LimitedRandomnessCheck.cpp b/clang-tools-extra/clang-tidy/cert/LimitedRandomnessCheck.cpp index 9733a4e7c1f56..0691787f1a90f 100644 --- a/clang-tools-extra/clang-tidy/cert/LimitedRandomnessCheck.cpp +++ b/clang-tools-extra/clang-tidy/cert/LimitedRandomnessCheck.cpp @@ -24,7 +24,7 @@ void LimitedRandomnessCheck::registerMatchers(MatchFinder *Finder) { } void LimitedRandomnessCheck::check(const MatchFinder::MatchResult &Result) { - std::string Msg = ""; + std::string Msg; if (getLangOpts().CPlusPlus) Msg = "; use C++11 random library instead"; diff --git a/clang-tools-extra/clang-tidy/cert/NonTrivialTypesLibcMemoryCallsCheck.h b/clang-tools-extra/clang-tidy/cert/NonTrivialTypesLibcMemoryCallsCheck.h index 41737ddae465e..e132de0c31407 100644 --- a/clang-tools-extra/clang-tidy/cert/NonTrivialTypesLibcMemoryCallsCheck.h +++ b/clang-tools-extra/clang-tidy/cert/NonTrivialTypesLibcMemoryCallsCheck.h @@ -41,4 +41,4 @@ class NonTrivialTypesLibcMemoryCallsCheck : public ClangTidyCheck { } // namespace tidy } // namespace clang -#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_NOTTRIVIALTYPESLIBCMEMORYCALLSCHECK_H +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_NONTRIVIALTYPESLIBCMEMORYCALLSCHECK_H diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/ProTypeVarargCheck.cpp b/clang-tools-extra/clang-tidy/cppcoreguidelines/ProTypeVarargCheck.cpp index b26cd59fd672a..83924f977d1a6 100644 --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/ProTypeVarargCheck.cpp +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/ProTypeVarargCheck.cpp @@ -11,6 +11,9 @@ #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Basic/TargetInfo.h" +#include "clang/Lex/PPCallbacks.h" +#include "clang/Lex/Preprocessor.h" +#include "clang/Lex/Token.h" using namespace clang::ast_matchers; @@ -57,6 +60,10 @@ static constexpr StringRef AllowedVariadics[] = { // clang-format on }; +static constexpr StringRef VaArgWarningMessage = + "do not use va_arg to define c-style vararg functions; " + "use variadic templates instead"; + namespace { AST_MATCHER(QualType, isVAList) { ASTContext &Context = Finder->getASTContext(); @@ -106,6 +113,21 @@ AST_MATCHER_P(AdjustedType, hasOriginalType, ast_matchers::internal::Matcher, InnerType) { return InnerType.matches(Node.getOriginalType(), Finder, Builder); } + +class VaArgPPCallbacks : public PPCallbacks { +public: + VaArgPPCallbacks(ProTypeVarargCheck *Check) : Check(Check) {} + + void MacroExpands(const Token &MacroNameTok, const MacroDefinition &MD, + SourceRange Range, const MacroArgs *Args) override { + if (MacroNameTok.getIdentifierInfo()->getName() == "va_arg") { + Check->diag(MacroNameTok.getLocation(), VaArgWarningMessage); + } + } + +private: + ProTypeVarargCheck *Check; +}; } // namespace void ProTypeVarargCheck::registerMatchers(MatchFinder *Finder) { @@ -125,6 +147,12 @@ void ProTypeVarargCheck::registerMatchers(MatchFinder *Finder) { this); } +void ProTypeVarargCheck::registerPPCallbacks(const SourceManager &SM, + Preprocessor *PP, + Preprocessor *ModuleExpanderPP) { + PP->addPPCallbacks(std::make_unique(this)); +} + static bool hasSingleVariadicArgumentWithValue(const CallExpr *C, uint64_t I) { const auto *FDecl = dyn_cast(C->getCalleeDecl()); if (!FDecl) @@ -153,9 +181,7 @@ void ProTypeVarargCheck::check(const MatchFinder::MatchResult &Result) { } if (const auto *Matched = Result.Nodes.getNodeAs("va_use")) { - diag(Matched->getExprLoc(), - "do not use va_arg to define c-style vararg functions; " - "use variadic templates instead"); + diag(Matched->getExprLoc(), VaArgWarningMessage); } if (const auto *Matched = Result.Nodes.getNodeAs("va_list")) { diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/ProTypeVarargCheck.h b/clang-tools-extra/clang-tidy/cppcoreguidelines/ProTypeVarargCheck.h index 06de06e3e8361..08b879abf130a 100644 --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/ProTypeVarargCheck.h +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/ProTypeVarargCheck.h @@ -28,6 +28,8 @@ class ProTypeVarargCheck : public ClangTidyCheck { return LangOpts.CPlusPlus; } void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, + Preprocessor *ModuleExpanderPP) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; }; diff --git a/clang-tools-extra/clang-tidy/llvmlibc/ImplementationInNamespaceCheck.cpp b/clang-tools-extra/clang-tidy/llvmlibc/ImplementationInNamespaceCheck.cpp index 42b697076b350..842bf43b2c45c 100644 --- a/clang-tools-extra/clang-tidy/llvmlibc/ImplementationInNamespaceCheck.cpp +++ b/clang-tools-extra/clang-tidy/llvmlibc/ImplementationInNamespaceCheck.cpp @@ -41,7 +41,6 @@ void ImplementationInNamespaceCheck::check( diag(MatchedDecl->getLocation(), "declaration must be declared within the '%0' namespace") << RequiredNamespace; - return; } } // namespace llvm_libc diff --git a/clang-tools-extra/clang-tidy/modernize/LoopConvertCheck.cpp b/clang-tools-extra/clang-tidy/modernize/LoopConvertCheck.cpp index 432d929057d14..b0b882be1a6c7 100644 --- a/clang-tools-extra/clang-tidy/modernize/LoopConvertCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/LoopConvertCheck.cpp @@ -829,7 +829,7 @@ bool LoopConvertCheck::isConvertible(ASTContext *Context, } else if (FixerKind == LFK_PseudoArray) { // This call is required to obtain the container. const auto *EndCall = Nodes.getNodeAs(EndCallName); - if (!EndCall || !dyn_cast(EndCall->getCallee())) + if (!EndCall || !isa(EndCall->getCallee())) return false; } return true; diff --git a/clang-tools-extra/clang-tidy/modernize/PassByValueCheck.cpp b/clang-tools-extra/clang-tidy/modernize/PassByValueCheck.cpp index 34079c65b68cb..2e1f2143947e9 100644 --- a/clang-tools-extra/clang-tidy/modernize/PassByValueCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/PassByValueCheck.cpp @@ -105,6 +105,75 @@ static bool paramReferredExactlyOnce(const CXXConstructorDecl *Ctor, return ExactlyOneUsageVisitor(ParamDecl).hasExactlyOneUsageIn(Ctor); } +/// Returns true if the given constructor is part of a lvalue/rvalue reference +/// pair, i.e. `Param` is of lvalue reference type, and there exists another +/// constructor such that: +/// - it has the same number of parameters as `Ctor`. +/// - the parameter at the same index as `Param` is an rvalue reference +/// of the same pointee type +/// - all other parameters have the same type as the corresponding parameter in +/// `Ctor` or are rvalue references with the same pointee type. +/// Examples: +/// A::A(const B& Param) +/// A::A(B&&) +/// +/// A::A(const B& Param, const C&) +/// A::A(B&& Param, C&&) +/// +/// A::A(const B&, const C& Param) +/// A::A(B&&, C&& Param) +/// +/// A::A(const B&, const C& Param) +/// A::A(const B&, C&& Param) +/// +/// A::A(const B& Param, int) +/// A::A(B&& Param, int) +static bool hasRValueOverload(const CXXConstructorDecl *Ctor, + const ParmVarDecl *Param) { + if (!Param->getType().getCanonicalType()->isLValueReferenceType()) { + // The parameter is passed by value. + return false; + } + const int ParamIdx = Param->getFunctionScopeIndex(); + const CXXRecordDecl *Record = Ctor->getParent(); + + // Check whether a ctor `C` forms a pair with `Ctor` under the aforementionned + // rules. + const auto IsRValueOverload = [&Ctor, ParamIdx](const CXXConstructorDecl *C) { + if (C == Ctor || C->isDeleted() || + C->getNumParams() != Ctor->getNumParams()) + return false; + for (int I = 0, E = C->getNumParams(); I < E; ++I) { + const clang::QualType CandidateParamType = + C->parameters()[I]->getType().getCanonicalType(); + const clang::QualType CtorParamType = + Ctor->parameters()[I]->getType().getCanonicalType(); + const bool IsLValueRValuePair = + CtorParamType->isLValueReferenceType() && + CandidateParamType->isRValueReferenceType() && + CandidateParamType->getPointeeType()->getUnqualifiedDesugaredType() == + CtorParamType->getPointeeType()->getUnqualifiedDesugaredType(); + if (I == ParamIdx) { + // The parameter of interest must be paired. + if (!IsLValueRValuePair) + return false; + } else { + // All other parameters can be similar or paired. + if (!(CandidateParamType == CtorParamType || IsLValueRValuePair)) + return false; + } + } + return true; + }; + + for (const auto *Candidate : Record->ctors()) { + if (IsRValueOverload(Candidate)) { + return true; + } + } + return false; +} + /// Find all references to \p ParamDecl across all of the /// redeclarations of \p Ctor. static SmallVector @@ -188,6 +257,10 @@ void PassByValueCheck::check(const MatchFinder::MatchResult &Result) { *Result.Context)) return; + // Do not trigger if we find a paired constructor with an rvalue. + if (hasRValueOverload(Ctor, ParamDecl)) + return; + auto Diag = diag(ParamDecl->getBeginLoc(), "pass by value and use std::move"); // If we received a `const&` type, we need to rewrite the function diff --git a/clang-tools-extra/clang-tidy/modernize/RawStringLiteralCheck.cpp b/clang-tools-extra/clang-tidy/modernize/RawStringLiteralCheck.cpp index 26b1d8ecdc319..40dda98b1e49b 100644 --- a/clang-tools-extra/clang-tidy/modernize/RawStringLiteralCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/RawStringLiteralCheck.cpp @@ -25,7 +25,7 @@ bool containsEscapes(StringRef HayStack, StringRef Escapes) { return false; while (BackSlash != StringRef::npos) { - if (Escapes.find(HayStack[BackSlash + 1]) == StringRef::npos) + if (!Escapes.contains(HayStack[BackSlash + 1])) return false; BackSlash = HayStack.find('\\', BackSlash + 2); } diff --git a/clang-tools-extra/clang-tidy/modernize/UseDefaultMemberInitCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseDefaultMemberInitCheck.cpp index 6e7e37236b19d..d57a88d668832 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseDefaultMemberInitCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseDefaultMemberInitCheck.cpp @@ -212,17 +212,14 @@ void UseDefaultMemberInitCheck::registerMatchers(MatchFinder *Finder) { InitBase); Finder->addMatcher( - cxxConstructorDecl( - isDefaultConstructor(), - forEachConstructorInitializer( - cxxCtorInitializer( - forField(unless(anyOf(getLangOpts().CPlusPlus20 - ? unless(anything()) - : isBitField(), - hasInClassInitializer(anything()), - hasParent(recordDecl(isUnion()))))), - withInitializer(Init)) - .bind("default"))), + cxxConstructorDecl(forEachConstructorInitializer( + cxxCtorInitializer( + forField(unless(anyOf( + getLangOpts().CPlusPlus20 ? unless(anything()) : isBitField(), + hasInClassInitializer(anything()), + hasParent(recordDecl(isUnion()))))), + withInitializer(Init)) + .bind("default"))), this); Finder->addMatcher( @@ -248,6 +245,14 @@ void UseDefaultMemberInitCheck::checkDefaultInit( const MatchFinder::MatchResult &Result, const CXXCtorInitializer *Init) { const FieldDecl *Field = Init->getAnyMember(); + // Check whether we have multiple hand-written constructors and bomb out, as + // it is hard to reconcile their sets of member initializers. + const auto *ClassDecl = dyn_cast(Field->getParent()); + if (llvm::count_if(ClassDecl->ctors(), [](const CXXConstructorDecl *Ctor) { + return !Ctor->isCopyOrMoveConstructor(); + }) > 1) + return; + SourceLocation StartLoc = Field->getBeginLoc(); if (StartLoc.isMacroID() && IgnoreMacros) return; diff --git a/clang-tools-extra/clang-tidy/readability/BracesAroundStatementsCheck.cpp b/clang-tools-extra/clang-tidy/readability/BracesAroundStatementsCheck.cpp index 7dc519c152828..07e962a07e843 100644 --- a/clang-tools-extra/clang-tidy/readability/BracesAroundStatementsCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/BracesAroundStatementsCheck.cpp @@ -81,7 +81,7 @@ static SourceLocation findEndLocation(const Stmt &S, const SourceManager &SM, SourceRange TokRange(Loc, TokEndLoc); StringRef Comment = Lexer::getSourceText( CharSourceRange::getTokenRange(TokRange), SM, Context->getLangOpts()); - if (Comment.startswith("/*") && Comment.find('\n') != StringRef::npos) { + if (Comment.startswith("/*") && Comment.contains('\n')) { // Multi-line block comment, insert brace before. break; } diff --git a/clang-tools-extra/clang-tidy/readability/IdentifierNamingCheck.cpp b/clang-tools-extra/clang-tidy/readability/IdentifierNamingCheck.cpp index cfbe79c525942..8cede1b2c17b5 100644 --- a/clang-tools-extra/clang-tidy/readability/IdentifierNamingCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/IdentifierNamingCheck.cpp @@ -1404,8 +1404,8 @@ IdentifierNamingCheck::getMacroFailureInfo(const Token &MacroNameTok, if (!Style.isActive()) return llvm::None; - return getFailureInfo("", MacroNameTok.getIdentifierInfo()->getName(), NULL, - Loc, Style.getStyles(), Style.getHNOption(), + return getFailureInfo("", MacroNameTok.getIdentifierInfo()->getName(), + nullptr, Loc, Style.getStyles(), Style.getHNOption(), SK_MacroDefinition, SM, IgnoreFailedSplit); } diff --git a/clang-tools-extra/clang-tidy/readability/NamedParameterCheck.cpp b/clang-tools-extra/clang-tidy/readability/NamedParameterCheck.cpp index 4f81dc49ded7c..c8a8edf67884e 100644 --- a/clang-tools-extra/clang-tidy/readability/NamedParameterCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/NamedParameterCheck.cpp @@ -71,7 +71,7 @@ void NamedParameterCheck::check(const MatchFinder::MatchResult &Result) { const char *Begin = SM.getCharacterData(Parm->getBeginLoc()); const char *End = SM.getCharacterData(Parm->getLocation()); StringRef Data(Begin, End - Begin); - if (Data.find("/*") != StringRef::npos) + if (Data.contains("/*")) continue; UnnamedParams.push_back(std::make_pair(Function, I)); diff --git a/clang-tools-extra/clang-tidy/utils/TransformerClangTidyCheck.h b/clang-tools-extra/clang-tidy/utils/TransformerClangTidyCheck.h index 9736e64e7c311..d26737935b1aa 100644 --- a/clang-tools-extra/clang-tidy/utils/TransformerClangTidyCheck.h +++ b/clang-tools-extra/clang-tidy/utils/TransformerClangTidyCheck.h @@ -54,7 +54,7 @@ class TransformerClangTidyCheck : public ClangTidyCheck { StringRef Name, ClangTidyContext *Context); /// Convenience overload of the constructor when the rule doesn't have any - /// dependies. + /// dependencies. TransformerClangTidyCheck(transformer::RewriteRule R, StringRef Name, ClangTidyContext *Context); diff --git a/clang-tools-extra/clangd/AST.cpp b/clang-tools-extra/clangd/AST.cpp index 3b59560377464..53b55e1579ec4 100644 --- a/clang-tools-extra/clangd/AST.cpp +++ b/clang-tools-extra/clangd/AST.cpp @@ -376,6 +376,24 @@ std::string printType(const QualType QT, const DeclContext &CurContext) { return OS.str(); } +bool hasReservedName(const Decl &D) { + if (const auto *ND = llvm::dyn_cast(&D)) + if (const auto *II = ND->getIdentifier()) + return isReservedName(II->getName()); + return false; +} + +bool hasReservedScope(const DeclContext &DC) { + for (const DeclContext *D = &DC; D; D = D->getParent()) { + if (D->isTransparentContext() || D->isInlineNamespace()) + continue; + if (const auto *ND = llvm::dyn_cast(D)) + if (hasReservedName(*ND)) + return true; + } + return false; +} + QualType declaredType(const TypeDecl *D) { if (const auto *CTSD = llvm::dyn_cast(D)) if (const auto *TSI = CTSD->getTypeAsWritten()) diff --git a/clang-tools-extra/clangd/AST.h b/clang-tools-extra/clangd/AST.h index 53d9f16a42627..afd591f0f4b48 100644 --- a/clang-tools-extra/clangd/AST.h +++ b/clang-tools-extra/clangd/AST.h @@ -74,6 +74,12 @@ std::string printObjCMethod(const ObjCMethodDecl &Method); // `MyClass()`, `MyClass(Category)`, and `MyProtocol`. std::string printObjCContainer(const ObjCContainerDecl &C); +/// Returns true if this is a NamedDecl with a reserved name. +bool hasReservedName(const Decl &); +/// Returns true if this scope would be written with a reserved name. +/// This does not include unwritten scope elements like __1 in std::__1::vector. +bool hasReservedScope(const DeclContext &); + /// Gets the symbol ID for a declaration. Returned SymbolID might be null. SymbolID getSymbolID(const Decl *D); diff --git a/clang-tools-extra/clangd/CMakeLists.txt b/clang-tools-extra/clangd/CMakeLists.txt index 056a3272ebd11..9c37cfe7b7001 100644 --- a/clang-tools-extra/clangd/CMakeLists.txt +++ b/clang-tools-extra/clangd/CMakeLists.txt @@ -131,6 +131,7 @@ add_clang_library(clangDaemon index/dex/PostingList.cpp index/dex/Trigram.cpp + refactor/InsertionPoint.cpp refactor/Rename.cpp refactor/Tweak.cpp diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp index 18539877ec976..76e3806f421e1 100644 --- a/clang-tools-extra/clangd/ClangdLSPServer.cpp +++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp @@ -555,7 +555,7 @@ void ClangdLSPServer::onInitialize(const InitializeParams &Params, }}, {"signatureHelpProvider", llvm::json::Object{ - {"triggerCharacters", {"(", ",", ")"}}, + {"triggerCharacters", {"(", ")", "{", "}", "<", ">", ","}}, }}, {"declarationProvider", true}, {"definitionProvider", true}, @@ -608,6 +608,7 @@ void ClangdLSPServer::onInitialize(const InitializeParams &Params, if (Opts.FoldingRanges) ServerCaps["foldingRangeProvider"] = true; + // FIXME: once inlayHints can be disabled in config, always advertise. if (Opts.InlayHints) ServerCaps["clangdInlayHintsProvider"] = true; @@ -1210,7 +1211,8 @@ void ClangdLSPServer::onCallHierarchyIncomingCalls( void ClangdLSPServer::onInlayHints(const InlayHintsParams &Params, Callback> Reply) { - Server->inlayHints(Params.textDocument.uri.file(), std::move(Reply)); + Server->inlayHints(Params.textDocument.uri.file(), Params.range, + std::move(Reply)); } void ClangdLSPServer::applyConfiguration( diff --git a/clang-tools-extra/clangd/ClangdLSPServer.h b/clang-tools-extra/clangd/ClangdLSPServer.h index bd7c1a05b0e22..0a5af762ad323 100644 --- a/clang-tools-extra/clangd/ClangdLSPServer.h +++ b/clang-tools-extra/clangd/ClangdLSPServer.h @@ -63,8 +63,8 @@ class ClangdLSPServer : private ClangdServer::Callbacks, return !T.hidden(); // only enable non-hidden tweaks. }; - /// Enable preview of InlayHints feature. - bool InlayHints = false; + /// Enable InlayHints feature. + bool InlayHints = true; /// Limit the number of references returned (0 means no limit). size_t ReferencesLimit = 0; diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp index e106c31ce0df7..69f37662c4ab7 100644 --- a/clang-tools-extra/clangd/ClangdServer.cpp +++ b/clang-tools-extra/clangd/ClangdServer.cpp @@ -759,12 +759,13 @@ void ClangdServer::incomingCalls( }); } -void ClangdServer::inlayHints(PathRef File, +void ClangdServer::inlayHints(PathRef File, llvm::Optional RestrictRange, Callback> CB) { - auto Action = [CB = std::move(CB)](Expected InpAST) mutable { + auto Action = [RestrictRange(std::move(RestrictRange)), + CB = std::move(CB)](Expected InpAST) mutable { if (!InpAST) return CB(InpAST.takeError()); - CB(clangd::inlayHints(InpAST->AST)); + CB(clangd::inlayHints(InpAST->AST, std::move(RestrictRange))); }; WorkScheduler->runWithAST("InlayHints", File, std::move(Action)); } diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h index 0589f4fc4214c..14be307e6fda8 100644 --- a/clang-tools-extra/clangd/ClangdServer.h +++ b/clang-tools-extra/clangd/ClangdServer.h @@ -264,7 +264,8 @@ class ClangdServer { Callback>); /// Resolve inlay hints for a given document. - void inlayHints(PathRef File, Callback>); + void inlayHints(PathRef File, llvm::Optional RestrictRange, + Callback>); /// Retrieve the top symbols from the workspace matching a query. void workspaceSymbols(StringRef Query, int Limit, diff --git a/clang-tools-extra/clangd/CodeComplete.cpp b/clang-tools-extra/clangd/CodeComplete.cpp index 1c6b91e348280..50388e08c30aa 100644 --- a/clang-tools-extra/clangd/CodeComplete.cpp +++ b/clang-tools-extra/clangd/CodeComplete.cpp @@ -895,14 +895,9 @@ struct ScoredSignature { // part of it. int paramIndexForArg(const CodeCompleteConsumer::OverloadCandidate &Candidate, int Arg) { - int NumParams = 0; - if (const auto *F = Candidate.getFunction()) { - NumParams = F->getNumParams(); - if (F->isVariadic()) - ++NumParams; - } else if (auto *T = Candidate.getFunctionType()) { + int NumParams = Candidate.getNumParams(); + if (auto *T = Candidate.getFunctionType()) { if (auto *Proto = T->getAs()) { - NumParams = Proto->getNumParams(); if (Proto->isVariadic()) ++NumParams; } @@ -923,7 +918,8 @@ class SignatureHelpCollector final : public CodeCompleteConsumer { void ProcessOverloadCandidates(Sema &S, unsigned CurrentArg, OverloadCandidate *Candidates, unsigned NumCandidates, - SourceLocation OpenParLoc) override { + SourceLocation OpenParLoc, + bool Braced) override { assert(!OpenParLoc.isInvalid()); SourceManager &SrcMgr = S.getSourceManager(); OpenParLoc = SrcMgr.getFileLoc(OpenParLoc); @@ -963,8 +959,9 @@ class SignatureHelpCollector final : public CodeCompleteConsumer { paramIndexForArg(Candidate, SigHelp.activeParameter); } - const auto *CCS = Candidate.CreateSignatureString( - CurrentArg, S, *Allocator, CCTUInfo, true); + const auto *CCS = + Candidate.CreateSignatureString(CurrentArg, S, *Allocator, CCTUInfo, + /*IncludeBriefComment=*/true, Braced); assert(CCS && "Expected the CodeCompletionString to be non-null"); ScoredSignatures.push_back(processOverloadCandidate( Candidate, *CCS, @@ -996,8 +993,7 @@ class SignatureHelpCollector final : public CodeCompleteConsumer { const ScoredSignature &R) { // Ordering follows: // - Less number of parameters is better. - // - Function is better than FunctionType which is better than - // Function Template. + // - Aggregate > Function > FunctionType > FunctionTemplate // - High score is better. // - Shorter signature is better. // - Alphabetically smaller is better. @@ -1009,15 +1005,22 @@ class SignatureHelpCollector final : public CodeCompleteConsumer { R.Quality.NumberOfOptionalParameters; if (L.Quality.Kind != R.Quality.Kind) { using OC = CodeCompleteConsumer::OverloadCandidate; - switch (L.Quality.Kind) { - case OC::CK_Function: - return true; - case OC::CK_FunctionType: - return R.Quality.Kind != OC::CK_Function; - case OC::CK_FunctionTemplate: - return false; - } - llvm_unreachable("Unknown overload candidate type."); + auto KindPriority = [&](OC::CandidateKind K) { + switch (K) { + case OC::CK_Aggregate: + return 1; + case OC::CK_Function: + return 2; + case OC::CK_FunctionType: + return 3; + case OC::CK_FunctionTemplate: + return 4; + case OC::CK_Template: + return 5; + } + llvm_unreachable("Unknown overload candidate type."); + }; + return KindPriority(L.Quality.Kind) < KindPriority(R.Quality.Kind); } if (L.Signature.label.size() != R.Signature.label.size()) return L.Signature.label.size() < R.Signature.label.size(); @@ -1162,24 +1165,15 @@ class ParamNameCollector final : public CodeCompleteConsumer { void ProcessOverloadCandidates(Sema &S, unsigned CurrentArg, OverloadCandidate *Candidates, unsigned NumCandidates, - SourceLocation OpenParLoc) override { + SourceLocation OpenParLoc, + bool Braced) override { assert(CurrentArg <= (unsigned)std::numeric_limits::max() && "too many arguments"); for (unsigned I = 0; I < NumCandidates; ++I) { - OverloadCandidate Candidate = Candidates[I]; - auto *Func = Candidate.getFunction(); - if (!Func || Func->getNumParams() <= CurrentArg) - continue; - auto *PVD = Func->getParamDecl(CurrentArg); - if (!PVD) - continue; - auto *Ident = PVD->getIdentifier(); - if (!Ident) - continue; - auto Name = Ident->getName(); - if (!Name.empty()) - ParamNames.insert(Name.str()); + if (const NamedDecl *ND = Candidates[I].getParamDecl(CurrentArg)) + if (const auto *II = ND->getIdentifier()) + ParamNames.emplace(II->getName()); } } diff --git a/clang-tools-extra/clangd/CompileCommands.cpp b/clang-tools-extra/clangd/CompileCommands.cpp index d707bf69eded4..5c98e40a87fdd 100644 --- a/clang-tools-extra/clangd/CompileCommands.cpp +++ b/clang-tools-extra/clangd/CompileCommands.cpp @@ -241,16 +241,38 @@ void CommandMangler::adjust(std::vector &Cmd, if (ArchOptCount < 2) IndicesToDrop.clear(); + // In some cases people may try to reuse the command from another file, e.g. + // { File: "foo.h", CommandLine: "clang foo.cpp" }. + // We assume the intent is to parse foo.h the same way as foo.cpp, or as if + // it were being included from foo.cpp. + // + // We're going to rewrite the command to refer to foo.h, and this may change + // its semantics (e.g. by parsing the file as C). If we do this, we should + // use transferCompileCommand to adjust the argv. + // In practice only the extension of the file matters, so do this only when + // it differs. + llvm::StringRef FileExtension = llvm::sys::path::extension(File); + llvm::Optional TransferFrom; + auto SawInput = [&](llvm::StringRef Input) { + if (llvm::sys::path::extension(Input) != FileExtension) + TransferFrom.emplace(Input); + }; + // Strip all the inputs and `--`. We'll put the input for the requested file // explicitly at the end of the flags. This ensures modifications done in the // following steps apply in more cases (like setting -x, which only affects // inputs that come after it). - for (auto *Input : ArgList.filtered(driver::options::OPT_INPUT)) + for (auto *Input : ArgList.filtered(driver::options::OPT_INPUT)) { + SawInput(Input->getValue(0)); IndicesToDrop.push_back(Input->getIndex()); + } // Anything after `--` is also treated as input, drop them as well. if (auto *DashDash = ArgList.getLastArgNoClaim(driver::options::OPT__DASH_DASH)) { - Cmd.resize(DashDash->getIndex() + 1); // +1 to account for Cmd[0]. + auto DashDashIndex = DashDash->getIndex() + 1; // +1 accounts for Cmd[0] + for (unsigned I = DashDashIndex; I < Cmd.size(); ++I) + SawInput(Cmd[I]); + Cmd.resize(DashDashIndex); } llvm::sort(IndicesToDrop); llvm::for_each(llvm::reverse(IndicesToDrop), @@ -262,6 +284,24 @@ void CommandMangler::adjust(std::vector &Cmd, Cmd.push_back("--"); Cmd.push_back(File.str()); + if (TransferFrom) { + tooling::CompileCommand TransferCmd; + TransferCmd.Filename = std::move(*TransferFrom); + TransferCmd.CommandLine = std::move(Cmd); + TransferCmd = transferCompileCommand(std::move(TransferCmd), File); + Cmd = std::move(TransferCmd.CommandLine); + + // Restore the canonical "driver --opts -- filename" form we expect. + // FIXME: This is ugly and coupled. Make transferCompileCommand ensure it? + assert(!Cmd.empty() && Cmd.back() == File); + Cmd.pop_back(); + if (!Cmd.empty() && Cmd.back() == "--") + Cmd.pop_back(); + assert(!llvm::is_contained(Cmd, "--")); + Cmd.push_back("--"); + Cmd.push_back(File.str()); + } + for (auto &Edit : Config::current().CompileFlags.Edits) Edit(Cmd); diff --git a/clang-tools-extra/clangd/ConfigCompile.cpp b/clang-tools-extra/clangd/ConfigCompile.cpp index 4f7f90847433e..18afdeb3cb5c6 100644 --- a/clang-tools-extra/clangd/ConfigCompile.cpp +++ b/clang-tools-extra/clangd/ConfigCompile.cpp @@ -253,6 +253,16 @@ struct FragmentCompiler { } void compile(Fragment::CompileFlagsBlock &&F) { + if (F.Compiler) + Out.Apply.push_back( + [Compiler(std::move(**F.Compiler))](const Params &, Config &C) { + C.CompileFlags.Edits.push_back( + [Compiler](std::vector &Args) { + if (!Args.empty()) + Args.front() = Compiler; + }); + }); + if (!F.Remove.empty()) { auto Remove = std::make_shared(); for (auto &A : F.Remove) diff --git a/clang-tools-extra/clangd/ConfigFragment.h b/clang-tools-extra/clangd/ConfigFragment.h index 63d2d75a22628..31c4636efa0b2 100644 --- a/clang-tools-extra/clangd/ConfigFragment.h +++ b/clang-tools-extra/clangd/ConfigFragment.h @@ -134,6 +134,16 @@ struct Fragment { /// /// This section modifies how the compile command is constructed. struct CompileFlagsBlock { + /// Override the compiler executable name to simulate. + /// + /// The name can affect how flags are parsed (clang++ vs clang). + /// If the executable name is in the --query-driver allowlist, then it will + /// be invoked to extract include paths. + /// + /// (That this simply replaces argv[0], and may mangle commands that use + /// more complicated drivers like ccache). + llvm::Optional> Compiler; + /// List of flags to append to the compile command. std::vector> Add; /// List of flags to remove from the compile command. diff --git a/clang-tools-extra/clangd/ConfigYAML.cpp b/clang-tools-extra/clangd/ConfigYAML.cpp index 6be11f199b489..0487c32815766 100644 --- a/clang-tools-extra/clangd/ConfigYAML.cpp +++ b/clang-tools-extra/clangd/ConfigYAML.cpp @@ -90,6 +90,10 @@ class Parser { void parse(Fragment::CompileFlagsBlock &F, Node &N) { DictParser Dict("CompileFlags", this); + Dict.handle("Compiler", [&](Node &N) { + if (auto Value = scalarValue(N, "Compiler")) + F.Compiler = std::move(*Value); + }); Dict.handle("Add", [&](Node &N) { if (auto Values = scalarValues(N)) F.Add = std::move(*Values); diff --git a/clang-tools-extra/clangd/Headers.cpp b/clang-tools-extra/clangd/Headers.cpp index 30cca7448be25..72da1be99283c 100644 --- a/clang-tools-extra/clangd/Headers.cpp +++ b/clang-tools-extra/clangd/Headers.cpp @@ -61,10 +61,19 @@ class IncludeStructure::RecordHeaders : public PPCallbacks, SM.getLineNumber(SM.getFileID(HashLoc), Inc.HashOffset) - 1; Inc.FileKind = FileKind; Inc.Directive = IncludeTok.getIdentifierInfo()->getPPKeywordID(); - if (File) - Inc.HeaderID = static_cast(Out->getOrCreateID(File)); if (LastPragmaKeepInMainFileLine == Inc.HashLine) Inc.BehindPragmaKeep = true; + if (File) { + IncludeStructure::HeaderID HID = Out->getOrCreateID(File); + Inc.HeaderID = static_cast(HID); + if (IsAngled) + if (auto StdlibHeader = stdlib::Header::named(Inc.Written)) { + auto &IDs = Out->StdlibHeaders[*StdlibHeader]; + // Few physical files for one stdlib header name, linear scan is ok. + if (!llvm::is_contained(IDs, HID)) + IDs.push_back(HID); + } + } } // Record include graph (not just for main-file includes) @@ -340,5 +349,155 @@ bool operator==(const Inclusion &LHS, const Inclusion &RHS) { std::tie(RHS.Directive, RHS.FileKind, RHS.HashOffset, RHS.HashLine, RHS.Resolved, RHS.Written); } + +namespace stdlib { +static llvm::StringRef *HeaderNames; +static std::pair *SymbolNames; +static unsigned *SymbolHeaderIDs; +static llvm::DenseMap *HeaderIDs; +// Maps symbol name -> Symbol::ID, within a namespace. +using NSSymbolMap = llvm::DenseMap; +static llvm::DenseMap *NamespaceSymbols; + +static int initialize() { + unsigned SymCount = 0; +#define SYMBOL(Name, NS, Header) ++SymCount; +#include "CSymbolMap.inc" +#include "StdSymbolMap.inc" +#undef SYMBOL + SymbolNames = new std::remove_reference_t[SymCount]; + SymbolHeaderIDs = + new std::remove_reference_t[SymCount]; + NamespaceSymbols = new std::remove_reference_t; + HeaderIDs = new std::remove_reference_t; + + auto AddNS = [&](llvm::StringRef NS) -> NSSymbolMap & { + auto R = NamespaceSymbols->try_emplace(NS, nullptr); + if (R.second) + R.first->second = new NSSymbolMap(); + return *R.first->second; + }; + + auto AddHeader = [&](llvm::StringRef Header) -> unsigned { + return HeaderIDs->try_emplace(Header, HeaderIDs->size()).first->second; + }; + + auto Add = [&, SymIndex(0)](llvm::StringRef Name, llvm::StringRef NS, + llvm::StringRef HeaderName) mutable { + if (NS == "None") + NS = ""; + + SymbolNames[SymIndex] = {NS, Name}; + SymbolHeaderIDs[SymIndex] = AddHeader(HeaderName); + + NSSymbolMap &NSSymbols = AddNS(NS); + NSSymbols.try_emplace(Name, SymIndex); + + ++SymIndex; + }; +#define SYMBOL(Name, NS, Header) Add(#Name, #NS, #Header); +#include "CSymbolMap.inc" +#include "StdSymbolMap.inc" +#undef SYMBOL + + HeaderNames = new llvm::StringRef[HeaderIDs->size()]; + for (const auto &E : *HeaderIDs) + HeaderNames[E.second] = E.first; + + return 0; +} + +static void ensureInitialized() { + static int Dummy = initialize(); + (void)Dummy; +} + +llvm::Optional
Header::named(llvm::StringRef Name) { + ensureInitialized(); + auto It = HeaderIDs->find(Name); + if (It == HeaderIDs->end()) + return llvm::None; + return Header(It->second); +} +llvm::StringRef Header::name() const { return HeaderNames[ID]; } +llvm::StringRef Symbol::scope() const { return SymbolNames[ID].first; } +llvm::StringRef Symbol::name() const { return SymbolNames[ID].second; } +llvm::Optional Symbol::named(llvm::StringRef Scope, + llvm::StringRef Name) { + ensureInitialized(); + if (NSSymbolMap *NSSymbols = NamespaceSymbols->lookup(Scope)) { + auto It = NSSymbols->find(Name); + if (It != NSSymbols->end()) + return Symbol(It->second); + } + return llvm::None; +} +Header Symbol::header() const { return Header(SymbolHeaderIDs[ID]); } +llvm::SmallVector
Symbol::headers() const { + return {header()}; // FIXME: multiple in case of ambiguity +} + +Recognizer::Recognizer() { ensureInitialized(); } + +NSSymbolMap *Recognizer::namespaceSymbols(const NamespaceDecl *D) { + auto It = NamespaceCache.find(D); + if (It != NamespaceCache.end()) + return It->second; + + NSSymbolMap *Result = [&]() -> NSSymbolMap * { + if (!D) // Nullptr means the global namespace + return NamespaceSymbols->lookup(""); + if (D->isAnonymousNamespace()) + return nullptr; + if (D->isInlineNamespace()) { + if (auto *Parent = llvm::dyn_cast_or_null(D->getParent())) + return namespaceSymbols(Parent); + return nullptr; + } + return NamespaceSymbols->lookup(printNamespaceScope(*D)); + }(); + NamespaceCache.try_emplace(D, Result); + return Result; +} + +llvm::Optional Recognizer::operator()(const Decl *D) { + // If D is std::vector::iterator, `vector` is the outer symbol to look up. + // We keep all the candidate DCs as some may turn out to be anon enums. + // Do this resolution lazily as we may turn out not to have a std namespace. + llvm::SmallVector IntermediateDecl; + const DeclContext *DC = D->getDeclContext(); + while (DC && !DC->isNamespace()) { + if (NamedDecl::classofKind(DC->getDeclKind())) + IntermediateDecl.push_back(DC); + DC = DC->getParent(); + } + NSSymbolMap *Symbols = namespaceSymbols(cast_or_null(DC)); + if (!Symbols) + return llvm::None; + + llvm::StringRef Name = [&]() -> llvm::StringRef { + for (const auto *SymDC : llvm::reverse(IntermediateDecl)) { + DeclarationName N = cast(SymDC)->getDeclName(); + if (const auto *II = N.getAsIdentifierInfo()) + return II->getName(); + if (!N.isEmpty()) + return ""; // e.g. operator<: give up + } + if (const auto *ND = llvm::dyn_cast(D)) + if (const auto *II = ND->getIdentifier()) + return II->getName(); + return ""; + }(); + if (Name.empty()) + return llvm::None; + + auto It = Symbols->find(Name); + if (It == Symbols->end()) + return llvm::None; + return Symbol(It->second); +} + +} // namespace stdlib + } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/Headers.h b/clang-tools-extra/clangd/Headers.h index c5f5746f25770..3b510325da042 100644 --- a/clang-tools-extra/clangd/Headers.h +++ b/clang-tools-extra/clangd/Headers.h @@ -32,8 +32,79 @@ #include namespace clang { +class Decl; +class NamespaceDecl; namespace clangd { +// clangd has a built-in database of standard library symbols. +namespace stdlib { +class Symbol; + +// A standard library header, such as +// Lightweight class, in fact just an index into a table. +class Header { +public: + static llvm::Optional
named(llvm::StringRef Name); + + friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Header &H) { + return OS << H.name(); + } + llvm::StringRef name() const; + +private: + Header(unsigned ID) : ID(ID) {} + unsigned ID; + friend Symbol; + friend llvm::DenseMapInfo
; + friend bool operator==(const Header &L, const Header &R) { + return L.ID == R.ID; + } +}; + +// A top-level standard library symbol, such as std::vector +// Lightweight class, in fact just an index into a table. +class Symbol { +public: + static llvm::Optional named(llvm::StringRef Scope, + llvm::StringRef Name); + + friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Symbol &S) { + return OS << S.scope() << S.name(); + } + llvm::StringRef scope() const; + llvm::StringRef name() const; + // The preferred header for this symbol (e.g. the suggested insertion). + Header header() const; + // Some symbols may be provided my multiple headers. + llvm::SmallVector
headers() const; + +private: + Symbol(unsigned ID) : ID(ID) {} + unsigned ID; + friend class Recognizer; + friend llvm::DenseMapInfo; + friend bool operator==(const Symbol &L, const Symbol &R) { + return L.ID == R.ID; + } +}; + +// A functor to find the stdlib::Symbol associated with a decl. +// +// For non-top-level decls (std::vector::iterator), returns the top-level +// symbol (std::vector). +class Recognizer { +public: + Recognizer(); + llvm::Optional operator()(const Decl *D); + +private: + using NSSymbolMap = llvm::DenseMap; + NSSymbolMap *namespaceSymbols(const NamespaceDecl *D); + llvm::DenseMap NamespaceCache; +}; + +} // namespace stdlib + /// Returns true if \p Include is literal include like "path" or . bool isLiteralInclude(llvm::StringRef Include); @@ -160,6 +231,8 @@ class IncludeStructure { // Maps HeaderID to the ids of the files included from it. llvm::DenseMap> IncludeChildren; + llvm::DenseMap> StdlibHeaders; + std::vector MainFileIncludes; // We reserve HeaderID(0) for the main file and will manually check for that @@ -250,13 +323,11 @@ namespace llvm { // Support HeaderIDs as DenseMap keys. template <> struct DenseMapInfo { static inline clang::clangd::IncludeStructure::HeaderID getEmptyKey() { - return static_cast( - DenseMapInfo::getEmptyKey()); + return static_cast(-1); } static inline clang::clangd::IncludeStructure::HeaderID getTombstoneKey() { - return static_cast( - DenseMapInfo::getTombstoneKey()); + return static_cast(-2); } static unsigned @@ -270,6 +341,38 @@ template <> struct DenseMapInfo { } }; +template <> struct DenseMapInfo { + static inline clang::clangd::stdlib::Header getEmptyKey() { + return clang::clangd::stdlib::Header(-1); + } + static inline clang::clangd::stdlib::Header getTombstoneKey() { + return clang::clangd::stdlib::Header(-2); + } + static unsigned getHashValue(const clang::clangd::stdlib::Header &H) { + return hash_value(H.ID); + } + static bool isEqual(const clang::clangd::stdlib::Header &LHS, + const clang::clangd::stdlib::Header &RHS) { + return LHS == RHS; + } +}; + +template <> struct DenseMapInfo { + static inline clang::clangd::stdlib::Symbol getEmptyKey() { + return clang::clangd::stdlib::Symbol(-1); + } + static inline clang::clangd::stdlib::Symbol getTombstoneKey() { + return clang::clangd::stdlib::Symbol(-2); + } + static unsigned getHashValue(const clang::clangd::stdlib::Symbol &S) { + return hash_value(S.ID); + } + static bool isEqual(const clang::clangd::stdlib::Symbol &LHS, + const clang::clangd::stdlib::Symbol &RHS) { + return LHS == RHS; + } +}; + } // namespace llvm #endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_HEADERS_H diff --git a/clang-tools-extra/clangd/IncludeCleaner.cpp b/clang-tools-extra/clangd/IncludeCleaner.cpp index 9e51f5430be80..85ba59519e8ad 100644 --- a/clang-tools-extra/clangd/IncludeCleaner.cpp +++ b/clang-tools-extra/clangd/IncludeCleaner.cpp @@ -26,6 +26,10 @@ namespace clang { namespace clangd { + +static bool AnalyzeStdlib = false; +void setIncludeCleanerAnalyzesStdlib(bool B) { AnalyzeStdlib = B; } + namespace { /// Crawler traverses the AST and feeds in the locations of (sometimes @@ -127,6 +131,10 @@ class ReferencedLocationCrawler void add(const Decl *D) { if (!D || !isNew(D->getCanonicalDecl())) return; + if (auto SS = StdRecognizer(D)) { + Result.Stdlib.insert(*SS); + return; + } // Special case RecordDecls, as it is common for them to be forward // declared multiple times. The most common cases are: // - Definition available in TU, only mark that one as usage. The rest is @@ -136,14 +144,14 @@ class ReferencedLocationCrawler // redecls. if (const auto *RD = llvm::dyn_cast(D)) { if (const auto *Definition = RD->getDefinition()) { - Result.insert(Definition->getLocation()); + Result.User.insert(Definition->getLocation()); return; } if (SM.isInMainFile(RD->getMostRecentDecl()->getLocation())) return; } for (const Decl *Redecl : D->redecls()) - Result.insert(Redecl->getLocation()); + Result.User.insert(Redecl->getLocation()); } bool isNew(const void *P) { return P && Visited.insert(P).second; } @@ -151,13 +159,14 @@ class ReferencedLocationCrawler ReferencedLocations &Result; llvm::DenseSet Visited; const SourceManager &SM; + stdlib::Recognizer StdRecognizer; }; // Given a set of referenced FileIDs, determines all the potentially-referenced // files and macros by traversing expansion/spelling locations of macro IDs. // This is used to map the referenced SourceLocations onto real files. -struct ReferencedFiles { - ReferencedFiles(const SourceManager &SM) : SM(SM) {} +struct ReferencedFilesBuilder { + ReferencedFilesBuilder(const SourceManager &SM) : SM(SM) {} llvm::DenseSet Files; llvm::DenseSet Macros; const SourceManager &SM; @@ -218,18 +227,23 @@ void findReferencedMacros(ParsedAST &AST, ReferencedLocations &Result) { continue; auto Loc = Macro->Info->getDefinitionLoc(); if (Loc.isValid()) - Result.insert(Loc); + Result.User.insert(Loc); + // FIXME: support stdlib macros } } -bool mayConsiderUnused(const Inclusion &Inc, ParsedAST &AST) { +static bool mayConsiderUnused(const Inclusion &Inc, ParsedAST &AST) { + if (Inc.BehindPragmaKeep) + return false; + // FIXME(kirillbobyrev): We currently do not support the umbrella headers. - // Standard Library headers are typically umbrella headers, and system - // headers are likely to be the Standard Library headers. Until we have a - // good support for umbrella headers and Standard Library headers, don't warn - // about them. - if (Inc.Written.front() == '<' || Inc.BehindPragmaKeep) + // System headers are likely to be standard library headers. + // Until we have good support for umbrella headers, don't warn about them. + if (Inc.Written.front() == '<') { + if (AnalyzeStdlib && stdlib::Header::named(Inc.Written)) + return true; return false; + } // Headers without include guards have side effects and are not // self-contained, skip them. assert(Inc.HeaderID); @@ -282,29 +296,36 @@ ReferencedLocations findReferencedLocations(ParsedAST &AST) { return Result; } -llvm::DenseSet -findReferencedFiles(const llvm::DenseSet &Locs, - const IncludeStructure &Includes, const SourceManager &SM) { - std::vector Sorted{Locs.begin(), Locs.end()}; +ReferencedFiles findReferencedFiles(const ReferencedLocations &Locs, + const IncludeStructure &Includes, + const SourceManager &SM) { + std::vector Sorted{Locs.User.begin(), Locs.User.end()}; llvm::sort(Sorted); // Group by FileID. - ReferencedFiles Files(SM); + ReferencedFilesBuilder Builder(SM); for (auto It = Sorted.begin(); It < Sorted.end();) { FileID FID = SM.getFileID(*It); - Files.add(FID, *It); + Builder.add(FID, *It); // Cheaply skip over all the other locations from the same FileID. // This avoids lots of redundant Loc->File lookups for the same file. do ++It; while (It != Sorted.end() && SM.isInFileID(*It, FID)); } + // If a header is not self-contained, we consider its symbols a logical part // of the including file. Therefore, mark the parents of all used // non-self-contained FileIDs as used. Perform this on FileIDs rather than // HeaderIDs, as each inclusion of a non-self-contained file is distinct. - llvm::DenseSet Result; - for (FileID ID : Files.Files) - Result.insert(headerResponsible(ID, SM, Includes)); - return Result; + llvm::DenseSet UserFiles; + for (FileID ID : Builder.Files) + UserFiles.insert(headerResponsible(ID, SM, Includes)); + + llvm::DenseSet StdlibFiles; + for (const auto &Symbol : Locs.Stdlib) + for (const auto &Header : Symbol.headers()) + StdlibFiles.insert(Header); + + return {std::move(UserFiles), std::move(StdlibFiles)}; } std::vector @@ -338,13 +359,13 @@ static bool isSpecialBuffer(FileID FID, const SourceManager &SM) { #endif llvm::DenseSet -translateToHeaderIDs(const llvm::DenseSet &Files, +translateToHeaderIDs(const ReferencedFiles &Files, const IncludeStructure &Includes, const SourceManager &SM) { trace::Span Tracer("IncludeCleaner::translateToHeaderIDs"); llvm::DenseSet TranslatedHeaderIDs; - TranslatedHeaderIDs.reserve(Files.size()); - for (FileID FID : Files) { + TranslatedHeaderIDs.reserve(Files.User.size()); + for (FileID FID : Files.User) { const FileEntry *FE = SM.getFileEntryForID(FID); if (!FE) { assert(isSpecialBuffer(FID, SM)); @@ -354,6 +375,9 @@ translateToHeaderIDs(const llvm::DenseSet &Files, assert(File); TranslatedHeaderIDs.insert(*File); } + for (stdlib::Header StdlibUsed : Files.Stdlib) + for (auto HID : Includes.StdlibHeaders.lookup(StdlibUsed)) + TranslatedHeaderIDs.insert(HID); return TranslatedHeaderIDs; } diff --git a/clang-tools-extra/clangd/IncludeCleaner.h b/clang-tools-extra/clangd/IncludeCleaner.h index 368cc70323278..198de95ea2fda 100644 --- a/clang-tools-extra/clangd/IncludeCleaner.h +++ b/clang-tools-extra/clangd/IncludeCleaner.h @@ -30,7 +30,11 @@ namespace clang { namespace clangd { -using ReferencedLocations = llvm::DenseSet; +struct ReferencedLocations { + llvm::DenseSet User; + llvm::DenseSet Stdlib; +}; + /// Finds locations of all symbols used in the main file. /// /// - RecursiveASTVisitor finds references to symbols and records their @@ -48,17 +52,22 @@ using ReferencedLocations = llvm::DenseSet; /// - err on the side of reporting all possible locations ReferencedLocations findReferencedLocations(ParsedAST &AST); +struct ReferencedFiles { + llvm::DenseSet User; + llvm::DenseSet Stdlib; +}; + /// Retrieves IDs of all files containing SourceLocations from \p Locs. /// The output only includes things SourceManager sees as files (not macro IDs). /// This can include , etc that are not true files. -llvm::DenseSet findReferencedFiles(const ReferencedLocations &Locs, - const IncludeStructure &Includes, - const SourceManager &SM); +ReferencedFiles findReferencedFiles(const ReferencedLocations &Locs, + const IncludeStructure &Includes, + const SourceManager &SM); /// Maps FileIDs to the internal IncludeStructure representation (HeaderIDs). /// FileIDs that are not true files ( etc) are dropped. llvm::DenseSet -translateToHeaderIDs(const llvm::DenseSet &Files, +translateToHeaderIDs(const ReferencedFiles &Files, const IncludeStructure &Includes, const SourceManager &SM); /// Retrieves headers that are referenced from the main file but not used. @@ -72,6 +81,15 @@ std::vector computeUnusedIncludes(ParsedAST &AST); std::vector issueUnusedIncludesDiagnostics(ParsedAST &AST, llvm::StringRef Code); +/// Affects whether standard library includes should be considered for removal. +/// This is off by default for now due to implementation limitations: +/// - macros are not tracked +/// - symbol names without a unique associated header are not tracked +/// - references to std-namespaced C types are not properly tracked: +/// instead of std::size_t -> we see ::size_t -> +/// FIXME: remove this hack once the implementation is good enough. +void setIncludeCleanerAnalyzesStdlib(bool B); + } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp index 6b9c8b1eee54f..60d20a38ecd26 100644 --- a/clang-tools-extra/clangd/InlayHints.cpp +++ b/clang-tools-extra/clangd/InlayHints.cpp @@ -8,20 +8,24 @@ #include "InlayHints.h" #include "HeuristicResolver.h" #include "ParsedAST.h" -#include "support/Logger.h" #include "clang/AST/DeclarationName.h" #include "clang/AST/ExprCXX.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/Basic/SourceManager.h" -#include "llvm/Support/raw_ostream.h" namespace clang { namespace clangd { +namespace { + +// For now, inlay hints are always anchored at the left or right of their range. +enum class HintSide { Left, Right }; class InlayHintVisitor : public RecursiveASTVisitor { public: - InlayHintVisitor(std::vector &Results, ParsedAST &AST) + InlayHintVisitor(std::vector &Results, ParsedAST &AST, + llvm::Optional RestrictRange) : Results(Results), AST(AST.getASTContext()), + RestrictRange(std::move(RestrictRange)), MainFileID(AST.getSourceManager().getMainFileID()), Resolver(AST.getHeuristicResolver()), TypeHintPolicy(this->AST.getPrintingPolicy()), @@ -88,7 +92,7 @@ class InlayHintVisitor : public RecursiveASTVisitor { QualType Deduced = AT->getDeducedType(); if (!Deduced.isNull()) { addTypeHint(D->getFunctionTypeLoc().getRParenLoc(), D->getReturnType(), - "-> "); + /*Prefix=*/"-> "); } } @@ -100,7 +104,7 @@ class InlayHintVisitor : public RecursiveASTVisitor { // but show hints for the individual bindings. if (auto *DD = dyn_cast(D)) { for (auto *Binding : DD->bindings()) { - addTypeHint(Binding->getLocation(), Binding->getType(), ": ", + addTypeHint(Binding->getLocation(), Binding->getType(), /*Prefix=*/": ", StructuredBindingPolicy); } return true; @@ -113,7 +117,7 @@ class InlayHintVisitor : public RecursiveASTVisitor { // (e.g. for `const auto& x = 42`, print `const int&`). // Alternatively, we could place the hint on the `auto` // (and then just print the type deduced for the `auto`). - addTypeHint(D->getLocation(), D->getType(), ": "); + addTypeHint(D->getLocation(), D->getType(), /*Prefix=*/": "); } } return true; @@ -160,8 +164,9 @@ class InlayHintVisitor : public RecursiveASTVisitor { if (!shouldHint(Args[I], Name)) continue; - addInlayHint(Args[I]->getSourceRange(), InlayHintKind::ParameterHint, - Name.str() + ": "); + addInlayHint(Args[I]->getSourceRange(), HintSide::Left, + InlayHintKind::ParameterHint, /*Prefix=*/"", Name, + /*Suffix=*/": "); } } @@ -313,20 +318,28 @@ class InlayHintVisitor : public RecursiveASTVisitor { return Result; } - void addInlayHint(SourceRange R, InlayHintKind Kind, llvm::StringRef Label) { + // We pass HintSide rather than SourceLocation because we want to ensure + // it is in the same file as the common file range. + void addInlayHint(SourceRange R, HintSide Side, InlayHintKind Kind, + llvm::StringRef Prefix, llvm::StringRef Label, + llvm::StringRef Suffix) { auto FileRange = toHalfOpenFileRange(AST.getSourceManager(), AST.getLangOpts(), R); if (!FileRange) return; + Range LSPRange{ + sourceLocToPosition(AST.getSourceManager(), FileRange->getBegin()), + sourceLocToPosition(AST.getSourceManager(), FileRange->getEnd())}; + Position LSPPos = Side == HintSide::Left ? LSPRange.start : LSPRange.end; + if (RestrictRange && + (LSPPos < RestrictRange->start || !(LSPPos < RestrictRange->end))) + return; // The hint may be in a file other than the main file (for example, a header // file that was included after the preamble), do not show in that case. if (!AST.getSourceManager().isWrittenInMainFile(FileRange->getBegin())) return; - Results.push_back(InlayHint{ - Range{ - sourceLocToPosition(AST.getSourceManager(), FileRange->getBegin()), - sourceLocToPosition(AST.getSourceManager(), FileRange->getEnd())}, - Kind, Label.str()}); + Results.push_back( + InlayHint{LSPPos, LSPRange, Kind, (Prefix + Label + Suffix).str()}); } void addTypeHint(SourceRange R, QualType T, llvm::StringRef Prefix) { @@ -341,11 +354,13 @@ class InlayHintVisitor : public RecursiveASTVisitor { std::string TypeName = T.getAsString(Policy); if (TypeName.length() < TypeNameLimit) - addInlayHint(R, InlayHintKind::TypeHint, std::string(Prefix) + TypeName); + addInlayHint(R, HintSide::Right, InlayHintKind::TypeHint, Prefix, + TypeName, /*Suffix=*/""); } std::vector &Results; ASTContext &AST; + llvm::Optional RestrictRange; FileID MainFileID; StringRef MainFileBuf; const HeuristicResolver *Resolver; @@ -361,9 +376,12 @@ class InlayHintVisitor : public RecursiveASTVisitor { static const size_t TypeNameLimit = 32; }; -std::vector inlayHints(ParsedAST &AST) { +} // namespace + +std::vector inlayHints(ParsedAST &AST, + llvm::Optional RestrictRange) { std::vector Results; - InlayHintVisitor Visitor(Results, AST); + InlayHintVisitor Visitor(Results, AST, std::move(RestrictRange)); Visitor.TraverseAST(AST.getASTContext()); // De-duplicate hints. Duplicates can sometimes occur due to e.g. explicit diff --git a/clang-tools-extra/clangd/InlayHints.h b/clang-tools-extra/clangd/InlayHints.h index 4157a45bd4379..a3c16788435d1 100644 --- a/clang-tools-extra/clangd/InlayHints.h +++ b/clang-tools-extra/clangd/InlayHints.h @@ -22,8 +22,10 @@ namespace clang { namespace clangd { class ParsedAST; -// Compute and return all inlay hints for a file. -std::vector inlayHints(ParsedAST &AST); +/// Compute and return inlay hints for a file. +/// If RestrictRange is set, return only hints whose location is in that range. +std::vector inlayHints(ParsedAST &AST, + llvm::Optional RestrictRange); } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/ParsedAST.cpp b/clang-tools-extra/clangd/ParsedAST.cpp index 4b96725de4417..732c36813800f 100644 --- a/clang-tools-extra/clangd/ParsedAST.cpp +++ b/clang-tools-extra/clangd/ParsedAST.cpp @@ -246,6 +246,49 @@ class ReplayPreamble : private PPCallbacks { std::vector MainFileTokens; }; +// Find -W and -Wno- options in ExtraArgs and apply them to Diags. +// +// This is used to handle ExtraArgs in clang-tidy configuration. +// We don't use clang's standard handling of this as we want slightly different +// behavior (e.g. we want to exclude these from -Wno-error). +void applyWarningOptions(llvm::ArrayRef ExtraArgs, + DiagnosticsEngine &Diags) { + for (llvm::StringRef Group : ExtraArgs) { + // Only handle args that are of the form -W[no-]. + // Other flags are possible but rare and deliberately out of scope. + llvm::SmallVector Members; + if (!Group.consume_front("-W") || Group.empty()) + continue; + bool Enable = !Group.consume_front("no-"); + if (Diags.getDiagnosticIDs()->getDiagnosticsInGroup( + diag::Flavor::WarningOrError, Group, Members)) + continue; + + // Upgrade (or downgrade) the severity of each diagnostic in the group. + // If -Werror is on, newly added warnings will be treated as errors. + // We don't want this, so keep track of them to fix afterwards. + bool NeedsWerrorExclusion = false; + for (diag::kind ID : Members) { + if (Enable) { + if (Diags.getDiagnosticLevel(ID, SourceLocation()) < + DiagnosticsEngine::Warning) { + Diags.setSeverity(ID, diag::Severity::Warning, SourceLocation()); + if (Diags.getWarningsAsErrors()) + NeedsWerrorExclusion = true; + } + } else { + Diags.setSeverity(ID, diag::Severity::Ignored, SourceLocation()); + } + } + if (NeedsWerrorExclusion) { + // FIXME: there's no API to suppress -Werror for single diagnostics. + // In some cases with sub-groups, we may end up erroneously + // downgrading diagnostics that were -Werror in the compile command. + Diags.setDiagnosticGroupWarningAsError(Group, false); + } + } +} + } // namespace llvm::Optional @@ -311,7 +354,32 @@ ParsedAST::build(llvm::StringRef Filename, const ParseInputs &Inputs, : "unknown error"); return None; } - if (!PreserveDiags) { + tidy::ClangTidyOptions ClangTidyOpts; + if (PreserveDiags) { + trace::Span Tracer("ClangTidyOpts"); + ClangTidyOpts = getTidyOptionsForFile(Inputs.ClangTidyProvider, Filename); + dlog("ClangTidy configuration for file {0}: {1}", Filename, + tidy::configurationAsText(ClangTidyOpts)); + + // If clang-tidy is configured to emit clang warnings, we should too. + // + // Such clang-tidy configuration consists of two parts: + // - ExtraArgs: ["-Wfoo"] causes clang to produce the warnings + // - Checks: "clang-diagnostic-foo" prevents clang-tidy filtering them out + // + // We treat these as clang warnings, so the Checks part is not relevant. + // We must enable the warnings specified in ExtraArgs. + // + // We *don't* want to change the compile command directly. this can have + // too many unexpected effects: breaking the command, interactions with + // -- and -Werror, etc. Besides, we've already parsed the command. + // Instead we parse the -W flags and handle them directly. + auto &Diags = Clang->getDiagnostics(); + if (ClangTidyOpts.ExtraArgsBefore) + applyWarningOptions(*ClangTidyOpts.ExtraArgsBefore, Diags); + if (ClangTidyOpts.ExtraArgs) + applyWarningOptions(*ClangTidyOpts.ExtraArgs, Diags); + } else { // Skips some analysis. Clang->getDiagnosticOpts().IgnoreWarnings = true; } @@ -348,10 +416,6 @@ ParsedAST::build(llvm::StringRef Filename, const ParseInputs &Inputs, // diagnostics. if (PreserveDiags) { trace::Span Tracer("ClangTidyInit"); - tidy::ClangTidyOptions ClangTidyOpts = - getTidyOptionsForFile(Inputs.ClangTidyProvider, Filename); - dlog("ClangTidy configuration for file {0}: {1}", Filename, - tidy::configurationAsText(ClangTidyOpts)); tidy::ClangTidyCheckFactories CTFactories; for (const auto &E : tidy::ClangTidyModuleRegistry::entries()) E.instantiate()->addCheckFactories(CTFactories); diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp index 6c3b1444852bd..42f452f74f970 100644 --- a/clang-tools-extra/clangd/Protocol.cpp +++ b/clang-tools-extra/clangd/Protocol.cpp @@ -1317,7 +1317,7 @@ llvm::json::Value toJSON(const CallHierarchyOutgoingCall &C) { bool fromJSON(const llvm::json::Value &Params, InlayHintsParams &R, llvm::json::Path P) { llvm::json::ObjectMapper O(Params, P); - return O && O.map("textDocument", R.textDocument); + return O && O.map("textDocument", R.textDocument) && O.map("range", R.range); } llvm::json::Value toJSON(InlayHintKind K) { @@ -1331,16 +1331,18 @@ llvm::json::Value toJSON(InlayHintKind K) { } llvm::json::Value toJSON(const InlayHint &H) { - return llvm::json::Object{ - {"range", H.range}, {"kind", H.kind}, {"label", H.label}}; + return llvm::json::Object{{"position", H.position}, + {"range", H.range}, + {"kind", H.kind}, + {"label", H.label}}; } bool operator==(const InlayHint &A, const InlayHint &B) { - return std::tie(A.kind, A.range, A.label) == - std::tie(B.kind, B.range, B.label); + return std::tie(A.position, A.range, A.kind, A.label) == + std::tie(B.position, B.range, B.kind, B.label); } bool operator<(const InlayHint &A, const InlayHint &B) { - return std::tie(A.kind, A.range, A.label) < - std::tie(B.kind, B.range, B.label); + return std::tie(A.position, A.range, A.kind, A.label) < + std::tie(B.position, B.range, B.kind, B.label); } static const char *toString(OffsetEncoding OE) { diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h index 819fa51821e00..d7ca580dceffc 100644 --- a/clang-tools-extra/clangd/Protocol.h +++ b/clang-tools-extra/clangd/Protocol.h @@ -1512,10 +1512,15 @@ struct CallHierarchyOutgoingCall { }; llvm::json::Value toJSON(const CallHierarchyOutgoingCall &); -/// The parameter of a `textDocument/inlayHints` request. +/// The parameter of a `clangd/inlayHints` request. +/// +/// This is a clangd extension. struct InlayHintsParams { /// The text document for which inlay hints are requested. TextDocumentIdentifier textDocument; + + /// If set, requests inlay hints for only part of the document. + llvm::Optional range; }; bool fromJSON(const llvm::json::Value &, InlayHintsParams &, llvm::json::Path); @@ -1542,19 +1547,27 @@ enum class InlayHintKind { llvm::json::Value toJSON(InlayHintKind); /// An annotation to be displayed inline next to a range of source code. +/// +/// This is a clangd extension. struct InlayHint { + /// The position between two characters where the hint should be displayed. + /// + /// For example, n parameter hint may be positioned before an argument. + Position position; + /// The range of source code to which the hint applies. - /// We provide the entire range, rather than just the endpoint - /// relevant to `position` (e.g. the start of the range for - /// InlayHintPosition::Before), to give clients the flexibility - /// to make choices like only displaying the hint while the cursor - /// is over the range, rather than displaying it all the time. + /// + /// For example, a parameter hint may have the argument as its range. + /// The range allows clients more flexibility of when/how to display the hint. Range range; - /// The type of hint. + /// The type of hint, such as a parameter hint. InlayHintKind kind; - /// The label that is displayed in the editor. + /// The label that is displayed in the editor, such as a parameter name. + /// + /// The label may contain punctuation and/or whitespace to allow it to read + /// naturally when placed inline with the code. std::string label; }; llvm::json::Value toJSON(const InlayHint &); diff --git a/clang-tools-extra/clangd/Quality.cpp b/clang-tools-extra/clangd/Quality.cpp index dc9c75b576708..900db4913f809 100644 --- a/clang-tools-extra/clangd/Quality.cpp +++ b/clang-tools-extra/clangd/Quality.cpp @@ -24,7 +24,6 @@ #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/SmallVector.h" -#include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Casting.h" #include "llvm/Support/FormatVariadic.h" @@ -35,11 +34,6 @@ namespace clang { namespace clangd { -static bool isReserved(llvm::StringRef Name) { - // FIXME: Should we exclude _Bool and others recognized by the standard? - return Name.size() >= 2 && Name[0] == '_' && - (isUppercase(Name[1]) || Name[1] == '_'); -} static bool hasDeclInMainFile(const Decl &D) { auto &SourceMgr = D.getASTContext().getSourceManager(); @@ -188,9 +182,10 @@ void SymbolQualitySignals::merge(const CodeCompletionResult &SemaCCResult) { if (SemaCCResult.Declaration) { ImplementationDetail |= isImplementationDetail(SemaCCResult.Declaration); if (auto *ID = SemaCCResult.Declaration->getIdentifier()) - ReservedName = ReservedName || isReserved(ID->getName()); + ReservedName = ReservedName || isReservedName(ID->getName()); } else if (SemaCCResult.Kind == CodeCompletionResult::RK_Macro) - ReservedName = ReservedName || isReserved(SemaCCResult.Macro->getName()); + ReservedName = + ReservedName || isReservedName(SemaCCResult.Macro->getName()); } void SymbolQualitySignals::merge(const Symbol &IndexResult) { @@ -198,7 +193,7 @@ void SymbolQualitySignals::merge(const Symbol &IndexResult) { ImplementationDetail |= (IndexResult.Flags & Symbol::ImplementationDetail); References = std::max(IndexResult.References, References); Category = categorize(IndexResult.SymInfo); - ReservedName = ReservedName || isReserved(IndexResult.Name); + ReservedName = ReservedName || isReservedName(IndexResult.Name); } float SymbolQualitySignals::evaluateHeuristics() const { diff --git a/clang-tools-extra/clangd/Selection.cpp b/clang-tools-extra/clangd/Selection.cpp index 0b10c7a3a6f9a..7dc8a868ea00e 100644 --- a/clang-tools-extra/clangd/Selection.cpp +++ b/clang-tools-extra/clangd/Selection.cpp @@ -58,7 +58,23 @@ void recordMetrics(const SelectionTree &S, const LangOptions &Lang) { SelectionUsedRecovery.record(0, LanguageLabel); // unused. } +// Return the range covering a node and all its children. SourceRange getSourceRange(const DynTypedNode &N) { + // DeclTypeTypeLoc::getSourceRange() is incomplete, which would lead to + // failing to descend into the child expression. + // decltype(2+2); + // ~~~~~~~~~~~~~ <-- correct range + // ~~~~~~~~ <-- range reported by getSourceRange() + // ~~~~~~~~~~~~ <-- range with this hack(i.e, missing closing paren) + // FIXME: Alter DecltypeTypeLoc to contain parentheses locations and get + // rid of this patch. + if (const auto *TL = N.get()) { + if (auto DT = TL->getAs()) { + SourceRange S = DT.getSourceRange(); + S.setEnd(DT.getUnderlyingExpr()->getEndLoc()); + return S; + } + } // MemberExprs to implicitly access anonymous fields should not claim any // tokens for themselves. Given: // struct A { struct { int b; }; }; @@ -623,11 +639,10 @@ class SelectionVisitor : public RecursiveASTVisitor { // Nodes *usually* nest nicely: a child's getSourceRange() lies within the // parent's, and a node (transitively) owns all tokens in its range. // - // Exception 1: child range claims tokens that should be owned by the parent. - // e.g. in `void foo(int);`, the FunctionTypeLoc should own - // `void (int)` but the parent FunctionDecl should own `foo`. - // To handle this case, certain nodes claim small token ranges *before* - // their children are traversed. (see earlySourceRange). + // Exception 1: when declarators nest, *inner* declarator is the *outer* type. + // e.g. void foo[5](int) is an array of functions. + // To handle this case, declarators are careful to only claim the tokens they + // own, rather than claim a range and rely on claim ordering. // // Exception 2: siblings both claim the same node. // e.g. `int x, y;` produces two sibling VarDecls. @@ -646,17 +661,6 @@ class SelectionVisitor : public RecursiveASTVisitor { // heuristics. We should consider only pruning critical TypeLoc nodes, to // be more robust. - // DeclTypeTypeLoc::getSourceRange() is incomplete, which would lead to - // failing - // to descend into the child expression. - // decltype(2+2); - // ~~~~~~~~~~~~~ <-- correct range - // ~~~~~~~~ <-- range reported by getSourceRange() - // ~~~~~~~~~~~~ <-- range with this hack(i.e, missing closing paren) - // FIXME: Alter DecltypeTypeLoc to contain parentheses locations and get - // rid of this patch. - if (auto DT = TL->getAs()) - S.setEnd(DT.getUnderlyingExpr()->getEndLoc()); // AttributedTypeLoc may point to the attribute's range, NOT the modified // type's range. if (auto AT = TL->getAs()) @@ -685,16 +689,13 @@ class SelectionVisitor : public RecursiveASTVisitor { } // Pushes a node onto the ancestor stack. Pairs with pop(). - // Performs early hit detection for some nodes (on the earlySourceRange). void push(DynTypedNode Node) { - SourceRange Early = earlySourceRange(Node); dlog("{1}push: {0}", printNodeToString(Node, PrintPolicy), indent()); Nodes.emplace_back(); Nodes.back().ASTNode = std::move(Node); Nodes.back().Parent = Stack.top(); Nodes.back().Selected = NoTokens; Stack.push(&Nodes.back()); - claimRange(Early, Nodes.back().Selected); } // Pops a node off the ancestor stack, and finalizes it. Pairs with push(). @@ -702,7 +703,7 @@ class SelectionVisitor : public RecursiveASTVisitor { void pop() { Node &N = *Stack.top(); dlog("{1}pop: {0}", printNodeToString(N.ASTNode, PrintPolicy), indent(-1)); - claimRange(getSourceRange(N.ASTNode), N.Selected); + claimTokensFor(N.ASTNode, N.Selected); if (N.Selected == NoTokens) N.Selected = SelectionTree::Unselected; if (N.Selected || !N.Children.empty()) { @@ -716,38 +717,73 @@ class SelectionVisitor : public RecursiveASTVisitor { Stack.pop(); } - // Returns the range of tokens that this node will claim directly, and - // is not available to the node's children. - // Usually empty, but sometimes children cover tokens but shouldn't own them. - SourceRange earlySourceRange(const DynTypedNode &N) { - if (const Decl *D = N.get()) { - // We want constructor name to be claimed by TypeLoc not the constructor - // itself. Similar for deduction guides, we rather want to select the - // underlying TypeLoc. - // FIXME: Unfortunately this doesn't work, even though RecursiveASTVisitor - // traverses the underlying TypeLoc inside DeclarationName, it is null for - // constructors. - if (isa(D) || isa(D)) - return SourceRange(); - // This will capture Field, Function, MSProperty, NonTypeTemplateParm and - // VarDecls. We want the name in the declarator to be claimed by the decl - // and not by any children. For example: - // void [[foo]](); - // int (*[[s]])(); - // struct X { int [[hash]] [32]; [[operator]] int();} - if (const auto *DD = llvm::dyn_cast(D)) - return DD->getLocation(); - } else if (const auto *CCI = N.get()) { - // : [[b_]](42) - return CCI->getMemberLocation(); + // Claim tokens for N, after processing its children. + // By default this claims all unclaimed tokens in getSourceRange(). + // We override this if we want to claim fewer tokens (e.g. there are gaps). + void claimTokensFor(const DynTypedNode &N, SelectionTree::Selection &Result) { + // CXXConstructExpr often shows implicit construction, like `string s;`. + // Don't associate any tokens with it unless there's some syntax like {}. + // This prevents it from claiming 's', its primary location. + if (const auto *CCE = N.get()) { + claimRange(CCE->getParenOrBraceRange(), Result); + return; + } + // ExprWithCleanups is always implicit. It often wraps CXXConstructExpr. + // Prevent it claiming 's' in the case above. + if (N.get()) + return; + + // Declarators nest "inside out", with parent types inside child ones. + // Instead of claiming the whole range (clobbering parent tokens), carefully + // claim the tokens owned by this node and non-declarator children. + // (We could manipulate traversal order instead, but this is easier). + // + // Non-declarator types nest normally, and are handled like other nodes. + // + // Example: + // Vec(*[2])(A)> is a Vec of arrays of pointers to functions, + // which accept A and return R. + // The TypeLoc hierarchy: + // Vec(*[2])(A)> m; + // Vec<#####################> TemplateSpecialization Vec + // --------[2]---------- `-Array + // -------*------------- `-Pointer + // ------(----)--------- `-Paren + // ------------(#######) `-Function + // R<###> |-TemplateSpecialization R + // int | `-Builtin int + // A<####> `-TemplateSpecialization A + // char `-Builtin char + // + // In each row + // --- represents unclaimed parts of the SourceRange. + // ### represents parts that children already claimed. + if (const auto *TL = N.get()) { + if (auto PTL = TL->getAs()) { + claimRange(PTL.getLParenLoc(), Result); + claimRange(PTL.getRParenLoc(), Result); + return; + } + if (auto ATL = TL->getAs()) { + claimRange(ATL.getBracketsRange(), Result); + return; + } + if (auto PTL = TL->getAs()) { + claimRange(PTL.getStarLoc(), Result); + return; + } + if (auto FTL = TL->getAs()) { + claimRange(SourceRange(FTL.getLParenLoc(), FTL.getEndLoc()), Result); + return; + } } - return SourceRange(); + claimRange(getSourceRange(N), Result); } // Perform hit-testing of a complete Node against the selection. // This runs for every node in the AST, and must be fast in common cases. // This is usually called from pop(), so we can take children into account. - // The existing state of Result is relevant (early/late claims can interact). + // The existing state of Result is relevant. void claimRange(SourceRange S, SelectionTree::Selection &Result) { for (const auto &ClaimedRange : UnclaimedExpandedTokens.erase(TokenBuf.expandedTokens(S))) diff --git a/clang-tools-extra/clangd/SemanticHighlighting.cpp b/clang-tools-extra/clangd/SemanticHighlighting.cpp index 76e810f5a3b83..796efedcc3991 100644 --- a/clang-tools-extra/clangd/SemanticHighlighting.cpp +++ b/clang-tools-extra/clangd/SemanticHighlighting.cpp @@ -468,7 +468,7 @@ class HighlightingsBuilder { const LangOptions &LangOpts; std::vector Tokens; std::map> ExtraModifiers; - const HeuristicResolver *Resolver; + const HeuristicResolver *Resolver = nullptr; // returned from addToken(InvalidLoc) HighlightingToken InvalidHighlightingToken; }; diff --git a/clang-tools-extra/clangd/SourceCode.h b/clang-tools-extra/clangd/SourceCode.h index 315d79a932df7..faed27d7c8c4e 100644 --- a/clang-tools-extra/clangd/SourceCode.h +++ b/clang-tools-extra/clangd/SourceCode.h @@ -16,6 +16,7 @@ #include "Protocol.h" #include "support/Context.h" #include "support/ThreadsafeFS.h" +#include "clang/Basic/CharInfo.h" #include "clang/Basic/Diagnostic.h" #include "clang/Basic/LangOptions.h" #include "clang/Basic/SourceLocation.h" @@ -27,7 +28,6 @@ #include "llvm/ADT/StringRef.h" #include "llvm/ADT/StringSet.h" #include "llvm/Support/Error.h" -#include "llvm/Support/SHA1.h" #include namespace clang { @@ -330,6 +330,13 @@ bool isProtoFile(SourceLocation Loc, const SourceManager &SourceMgr); bool isSelfContainedHeader(const FileEntry *FE, FileID ID, const SourceManager &SM, HeaderSearch &HeaderInfo); +/// Returns true if Name is reserved, like _Foo or __Vector_base. +inline bool isReservedName(llvm::StringRef Name) { + // This doesn't catch all cases, but the most common. + return Name.size() >= 2 && Name[0] == '_' && + (isUppercase(Name[1]) || Name[1] == '_'); +} + } // namespace clangd } // namespace clang #endif diff --git a/clang-tools-extra/clangd/TUScheduler.cpp b/clang-tools-extra/clangd/TUScheduler.cpp index 33df549d238de..43b4d8ca8881f 100644 --- a/clang-tools-extra/clangd/TUScheduler.cpp +++ b/clang-tools-extra/clangd/TUScheduler.cpp @@ -856,7 +856,6 @@ void ASTWorker::update(ParseInputs Inputs, WantDiagnostics WantDiags, // LatestPreamble is only populated by ASTWorker thread. return LatestPreamble || !PreambleRequests.empty() || Done; }); - return; }; startTask(TaskName, std::move(Task), UpdateType{WantDiags, ContentChanged}, TUScheduler::NoInvalidation); diff --git a/clang-tools-extra/clangd/index/FileIndex.cpp b/clang-tools-extra/clangd/index/FileIndex.cpp index 8960602a01901..fa58c6ff6fb19 100644 --- a/clang-tools-extra/clangd/index/FileIndex.cpp +++ b/clang-tools-extra/clangd/index/FileIndex.cpp @@ -56,6 +56,9 @@ SlabTuple indexSymbols(ASTContext &AST, Preprocessor &PP, CollectorOpts.CountReferences = false; CollectorOpts.Origin = SymbolOrigin::Dynamic; CollectorOpts.CollectMainFileRefs = CollectMainFileRefs; + // We want stdlib implementation details in the index only if we've opened the + // file in question. This does means xrefs won't work, though. + CollectorOpts.CollectReserved = IsIndexMainAST; index::IndexingOptions IndexOpts; // We only need declarations, because we don't count references. diff --git a/clang-tools-extra/clangd/index/SymbolCollector.cpp b/clang-tools-extra/clangd/index/SymbolCollector.cpp index 639003d667f45..cccc2c4e8f3d4 100644 --- a/clang-tools-extra/clangd/index/SymbolCollector.cpp +++ b/clang-tools-extra/clangd/index/SymbolCollector.cpp @@ -356,6 +356,10 @@ bool SymbolCollector::shouldCollectSymbol(const NamedDecl &ND, // Avoid indexing internal symbols in protobuf generated headers. if (isPrivateProtoDecl(ND)) return false; + if (!Opts.CollectReserved && + (hasReservedName(ND) || hasReservedScope(*ND.getDeclContext()))) + return false; + return true; } diff --git a/clang-tools-extra/clangd/index/SymbolCollector.h b/clang-tools-extra/clangd/index/SymbolCollector.h index a451a81ed29cb..0be6d2ad6c5b9 100644 --- a/clang-tools-extra/clangd/index/SymbolCollector.h +++ b/clang-tools-extra/clangd/index/SymbolCollector.h @@ -81,6 +81,9 @@ class SymbolCollector : public index::IndexDataConsumer { bool CollectMainFileSymbols = true; /// Collect references to main-file symbols. bool CollectMainFileRefs = false; + /// Collect symbols with reserved names, like __Vector_base. + /// This does not currently affect macros (many like _WIN32 are important!) + bool CollectReserved = false; /// If set to true, SymbolCollector will collect doc for all symbols. /// Note that documents of symbols being indexed for completion will always /// be collected regardless of this option. diff --git a/clang-tools-extra/clangd/refactor/InsertionPoint.cpp b/clang-tools-extra/clangd/refactor/InsertionPoint.cpp new file mode 100644 index 0000000000000..ce5f3a5a1d5fd --- /dev/null +++ b/clang-tools-extra/clangd/refactor/InsertionPoint.cpp @@ -0,0 +1,157 @@ +//===--- InsertionPoint.cpp - Where should we add new code? ---------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "refactor/InsertionPoint.h" +#include "support/Logger.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/DeclObjC.h" +#include "clang/AST/DeclTemplate.h" +#include "clang/Basic/SourceManager.h" + +namespace clang { +namespace clangd { +namespace { + +// Choose the decl to insert before, according to an anchor. +// Nullptr means insert at end of DC. +// None means no valid place to insert. +llvm::Optional insertionDecl(const DeclContext &DC, + const Anchor &A) { + bool LastMatched = false; + bool ReturnNext = false; + for (const auto *D : DC.decls()) { + if (D->isImplicit()) + continue; + if (ReturnNext) + return D; + + const Decl *NonTemplate = D; + if (auto *TD = llvm::dyn_cast(D)) + NonTemplate = TD->getTemplatedDecl(); + bool Matches = A.Match(NonTemplate); + dlog(" {0} {1} {2}", Matches, D->getDeclKindName(), D); + + switch (A.Direction) { + case Anchor::Above: + if (Matches && !LastMatched) { + // Special case: if "above" matches an access specifier, we actually + // want to insert below it! + if (llvm::isa(D)) { + ReturnNext = true; + continue; + } + return D; + } + break; + case Anchor::Below: + if (LastMatched && !Matches) + return D; + break; + } + + LastMatched = Matches; + } + if (ReturnNext || (LastMatched && A.Direction == Anchor::Below)) + return nullptr; + return llvm::None; +} + +SourceLocation beginLoc(const Decl &D) { + auto Loc = D.getBeginLoc(); + if (RawComment *Comment = D.getASTContext().getRawCommentForDeclNoCache(&D)) { + auto CommentLoc = Comment->getBeginLoc(); + if (CommentLoc.isValid() && Loc.isValid() && + D.getASTContext().getSourceManager().isBeforeInTranslationUnit( + CommentLoc, Loc)) + Loc = CommentLoc; + } + return Loc; +} + +bool any(const Decl *D) { return true; } + +SourceLocation endLoc(const DeclContext &DC) { + const Decl *D = llvm::cast(&DC); + if (auto *OCD = llvm::dyn_cast(D)) + return OCD->getAtEndRange().getBegin(); + return D->getEndLoc(); +} + +AccessSpecifier getAccessAtEnd(const CXXRecordDecl &C) { + AccessSpecifier Spec = (C.getTagKind() == TTK_Class ? AS_private : AS_public); + for (const auto *D : C.decls()) + if (const auto *ASD = llvm::dyn_cast(D)) + Spec = ASD->getAccess(); + return Spec; +} + +} // namespace + +SourceLocation insertionPoint(const DeclContext &DC, + llvm::ArrayRef Anchors) { + dlog("Looking for insertion point in {0}", DC.getDeclKindName()); + for (const auto &A : Anchors) { + dlog(" anchor ({0})", A.Direction == Anchor::Above ? "above" : "below"); + if (auto D = insertionDecl(DC, A)) { + dlog(" anchor matched before {0}", *D); + return *D ? beginLoc(**D) : endLoc(DC); + } + } + dlog("no anchor matched"); + return SourceLocation(); +} + +llvm::Expected +insertDecl(llvm::StringRef Code, const DeclContext &DC, + llvm::ArrayRef Anchors) { + auto Loc = insertionPoint(DC, Anchors); + // Fallback: insert at the end. + if (Loc.isInvalid()) + Loc = endLoc(DC); + const auto &SM = DC.getParentASTContext().getSourceManager(); + if (!SM.isWrittenInSameFile(Loc, cast(DC).getLocation())) + return error("{0} body in wrong file: {1}", DC.getDeclKindName(), + Loc.printToString(SM)); + return tooling::Replacement(SM, Loc, 0, Code); +} + +SourceLocation insertionPoint(const CXXRecordDecl &InClass, + std::vector Anchors, + AccessSpecifier Protection) { + for (auto &A : Anchors) + A.Match = [Inner(std::move(A.Match)), Protection](const Decl *D) { + return D->getAccess() == Protection && Inner(D); + }; + return insertionPoint(InClass, Anchors); +} + +llvm::Expected insertDecl(llvm::StringRef Code, + const CXXRecordDecl &InClass, + std::vector Anchors, + AccessSpecifier Protection) { + // Fallback: insert at the bottom of the relevant access section. + Anchors.push_back({any, Anchor::Below}); + auto Loc = insertionPoint(InClass, std::move(Anchors), Protection); + std::string CodeBuffer; + auto &SM = InClass.getASTContext().getSourceManager(); + // Fallback: insert at the end of the class. Check if protection matches! + if (Loc.isInvalid()) { + Loc = InClass.getBraceRange().getEnd(); + if (Protection != getAccessAtEnd(InClass)) { + CodeBuffer = (getAccessSpelling(Protection) + ":\n" + Code).str(); + Code = CodeBuffer; + } + } + if (!SM.isWrittenInSameFile(Loc, InClass.getLocation())) + return error("Class body in wrong file: {0}", Loc.printToString(SM)); + return tooling::Replacement(SM, Loc, 0, Code); +} + +} // namespace clangd +} // namespace clang diff --git a/clang-tools-extra/clangd/refactor/InsertionPoint.h b/clang-tools-extra/clangd/refactor/InsertionPoint.h new file mode 100644 index 0000000000000..eee158b77e1f6 --- /dev/null +++ b/clang-tools-extra/clangd/refactor/InsertionPoint.h @@ -0,0 +1,53 @@ +//===--- InsertionPoint.h - Where should we add new code? --------*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "clang/AST/DeclCXX.h" +#include "clang/Basic/Specifiers.h" +#include "clang/Tooling/Core/Replacement.h" + +namespace clang { +namespace clangd { + +// An anchor describes where to insert code into a decl sequence. +// +// It allows inserting above or below a block of decls matching some criterion. +// For example, "insert after existing constructors". +struct Anchor { + // A predicate describing which decls are considered part of a block. + // Match need not handle TemplateDecls, which are unwrapped before matching. + std::function Match; + // Whether the insertion point should be before or after the matching block. + enum Dir { Above, Below } Direction = Below; +}; + +// Returns the point to insert a declaration according to Anchors. +// Anchors are tried in order. For each, the first matching location is chosen. +SourceLocation insertionPoint(const DeclContext &Ctx, + llvm::ArrayRef Anchors); + +// Returns an edit inserting Code inside Ctx. +// Location is chosen according to Anchors, falling back to the end of Ctx. +// Fails if the chosen insertion point is in a different file than Ctx itself. +llvm::Expected insertDecl(llvm::StringRef Code, + const DeclContext &Ctx, + llvm::ArrayRef Anchors); + +// Variant for C++ classes that ensures the right access control. +SourceLocation insertionPoint(const CXXRecordDecl &InClass, + std::vector Anchors, + AccessSpecifier Protection); + +// Variant for C++ classes that ensures the right access control. +// May insert a new access specifier if needed. +llvm::Expected insertDecl(llvm::StringRef Code, + const CXXRecordDecl &InClass, + std::vector Anchors, + AccessSpecifier Protection); + +} // namespace clangd +} // namespace clang diff --git a/clang-tools-extra/clangd/test/initialize-params.test b/clang-tools-extra/clangd/test/initialize-params.test index a79f1075118ac..e5701876684e3 100644 --- a/clang-tools-extra/clangd/test/initialize-params.test +++ b/clang-tools-extra/clangd/test/initialize-params.test @@ -7,6 +7,7 @@ # CHECK-NEXT: "capabilities": { # CHECK-NEXT: "astProvider": true, # CHECK-NEXT: "callHierarchyProvider": true, +# CHECK-NEXT: "clangdInlayHintsProvider": true, # CHECK-NEXT: "codeActionProvider": true, # CHECK-NEXT: "compilationDatabase": { # CHECK-NEXT: "automaticReload": true @@ -107,8 +108,12 @@ # CHECK-NEXT: "signatureHelpProvider": { # CHECK-NEXT: "triggerCharacters": [ # CHECK-NEXT: "(", -# CHECK-NEXT: ",", -# CHECK-NEXT: ")" +# CHECK-NEXT: ")", +# CHECK-NEXT: "{", +# CHECK-NEXT: "}", +# CHECK-NEXT: "<", +# CHECK-NEXT: ">" +# CHECK-NEXT: "," # CHECK-NEXT: ] # CHECK-NEXT: }, # CHECK-NEXT: "textDocumentSync": { diff --git a/clang-tools-extra/clangd/test/inlayHints.test b/clang-tools-extra/clangd/test/inlayHints.test new file mode 100644 index 0000000000000..48b1d83dca25f --- /dev/null +++ b/clang-tools-extra/clangd/test/inlayHints.test @@ -0,0 +1,45 @@ +# RUN: clangd -lit-test < %s | FileCheck %s +{"jsonrpc":"2.0","id":0,"method":"initialize","params":{}} +--- +{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{ + "uri":"test:///main.cpp", + "languageId":"cpp", + "version":1, + "text":"int foo(int bar);\nint x = foo(42);\nint y = foo(42);" +}}} +--- +{"jsonrpc":"2.0","id":1,"method":"clangd/inlayHints","params":{ + "textDocument":{"uri":"test:///main.cpp"}, + "range":{ + "start": {"line":1,"character":0}, + "end": {"line":2,"character":0} + } +}} +# CHECK: "id": 1, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": [ +# CHECK-NEXT: { +# CHECK-NEXT: "kind": "parameter", +# CHECK-NEXT: "label": "bar: ", +# CHECK-NEXT: "position": { +# CHECK-NEXT: "character": 12, +# CHECK-NEXT: "line": 1 +# CHECK-NEXT: }, +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 14, +# CHECK-NEXT: "line": 1 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 12, +# CHECK-NEXT: "line": 1 +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: ] +# CHECK-NEXT:} +--- +{"jsonrpc":"2.0","id":100,"method":"shutdown"} +--- +{"jsonrpc":"2.0","method":"exit"} + diff --git a/clang-tools-extra/clangd/tool/ClangdMain.cpp b/clang-tools-extra/clangd/tool/ClangdMain.cpp index 08631a31eda6c..6051142337809 100644 --- a/clang-tools-extra/clangd/tool/ClangdMain.cpp +++ b/clang-tools-extra/clangd/tool/ClangdMain.cpp @@ -12,6 +12,7 @@ #include "Config.h" #include "ConfigProvider.h" #include "Feature.h" +#include "IncludeCleaner.h" #include "PathMapping.h" #include "Protocol.h" #include "TidyProvider.h" @@ -251,6 +252,15 @@ opt HeaderInsertion{ "Never insert #include directives as part of code completion")), }; +opt IncludeCleanerStdlib{ + "include-cleaner-stdlib", + cat(Features), + desc("Apply include-cleaner analysis to standard library headers " + "(immature!)"), + init(false), + Hidden, +}; + opt HeaderInsertionDecorators{ "header-insertion-decorators", cat(Features), @@ -317,8 +327,14 @@ opt FoldingRanges{ Hidden, }; -opt InlayHints{"inlay-hints", cat(Features), - desc("Enable preview of InlayHints feature"), init(false)}; +opt InlayHints{ + "inlay-hints", + cat(Features), + desc("Enable InlayHints feature"), + init(ClangdLSPServer::Options().InlayHints), + // FIXME: allow inlayHints to be disabled in Config and remove this option. + Hidden, +}; opt WorkerThreadsCount{ "j", @@ -932,6 +948,7 @@ clangd accepts flags on the commandline, and in the CLANGD_FLAGS environment var }; if (ForceOffsetEncoding != OffsetEncoding::UnsupportedEncoding) Opts.Encoding = ForceOffsetEncoding; + setIncludeCleanerAnalyzesStdlib(IncludeCleanerStdlib); if (CheckFile.getNumOccurrences()) { llvm::SmallString<256> Path; diff --git a/clang-tools-extra/clangd/unittests/ASTTests.cpp b/clang-tools-extra/clangd/unittests/ASTTests.cpp index 44dc8f0c69cf5..53d399eed72a1 100644 --- a/clang-tools-extra/clangd/unittests/ASTTests.cpp +++ b/clang-tools-extra/clangd/unittests/ASTTests.cpp @@ -427,6 +427,29 @@ TEST(ClangdAST, GetAttributes) { Contains(attrKind(attr::Unlikely))); } +TEST(ClangdAST, HasReservedName) { + ParsedAST AST = TestTU::withCode(R"cpp( + void __foo(); + namespace std { + inline namespace __1 { class error_code; } + namespace __detail { int secret; } + } + )cpp") + .build(); + + EXPECT_TRUE(hasReservedName(findUnqualifiedDecl(AST, "__foo"))); + EXPECT_FALSE( + hasReservedScope(*findUnqualifiedDecl(AST, "__foo").getDeclContext())); + + EXPECT_FALSE(hasReservedName(findUnqualifiedDecl(AST, "error_code"))); + EXPECT_FALSE(hasReservedScope( + *findUnqualifiedDecl(AST, "error_code").getDeclContext())); + + EXPECT_FALSE(hasReservedName(findUnqualifiedDecl(AST, "secret"))); + EXPECT_TRUE( + hasReservedScope(*findUnqualifiedDecl(AST, "secret").getDeclContext())); +} + } // namespace } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/unittests/CMakeLists.txt b/clang-tools-extra/clangd/unittests/CMakeLists.txt index 3c17bcdbc17a4..29d177435f486 100644 --- a/clang-tools-extra/clangd/unittests/CMakeLists.txt +++ b/clang-tools-extra/clangd/unittests/CMakeLists.txt @@ -62,6 +62,7 @@ add_unittest(ClangdUnitTests ClangdTests IndexActionTests.cpp IndexTests.cpp InlayHintTests.cpp + InsertionPointTests.cpp JSONTransportTests.cpp LoggerTests.cpp LSPBinderTests.cpp diff --git a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp index 2d4236d0763f3..52dee0fdc0e21 100644 --- a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp +++ b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp @@ -1212,6 +1212,10 @@ struct ExpectedParameter { std::string Text; std::pair Offsets; }; +llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, + const ExpectedParameter &P) { + return OS << P.Text; +} MATCHER_P(ParamsAre, P, "") { if (P.size() != arg.parameters.size()) return false; @@ -1260,6 +1264,55 @@ TEST(SignatureHelpTest, Overloads) { EXPECT_EQ(0, Results.activeParameter); } +TEST(SignatureHelpTest, Constructors) { + std::string Top = R"cpp( + struct S { + S(int); + S(const S &) = delete; + }; + )cpp"; + + auto CheckParenInit = [&](std::string Init) { + EXPECT_THAT(signatures(Top + Init).signatures, + UnorderedElementsAre(Sig("S([[int]])"))) + << Init; + }; + CheckParenInit("S s(^);"); + CheckParenInit("auto s = S(^);"); + CheckParenInit("auto s = new S(^);"); + + auto CheckBracedInit = [&](std::string Init) { + EXPECT_THAT(signatures(Top + Init).signatures, + UnorderedElementsAre(Sig("S{[[int]]}"))) + << Init; + }; + CheckBracedInit("S s{^};"); + CheckBracedInit("S s = {^};"); + CheckBracedInit("auto s = S{^};"); + // FIXME: doesn't work: no ExpectedType set in ParseCXXNewExpression. + // CheckBracedInit("auto s = new S{^};"); + CheckBracedInit("int x(S); int i = x({^});"); +} + +TEST(SignatureHelpTest, Aggregates) { + std::string Top = R"cpp( + struct S { + int a, b, c, d; + }; + )cpp"; + auto AggregateSig = Sig("S{[[int a]], [[int b]], [[int c]], [[int d]]}"); + EXPECT_THAT(signatures(Top + "S s{^}").signatures, + UnorderedElementsAre(AggregateSig, Sig("S{}"), + Sig("S{[[const S &]]}"), + Sig("S{[[S &&]]}"))); + EXPECT_THAT(signatures(Top + "S s{1,^}").signatures, + ElementsAre(AggregateSig)); + EXPECT_EQ(signatures(Top + "S s{1,^}").activeParameter, 1); + EXPECT_THAT(signatures(Top + "S s{.c=3,^}").signatures, + ElementsAre(AggregateSig)); + EXPECT_EQ(signatures(Top + "S s{.c=3,^}").activeParameter, 3); +} + TEST(SignatureHelpTest, OverloadInitListRegression) { auto Results = signatures(R"cpp( struct A {int x;}; @@ -3453,6 +3506,25 @@ TEST(SignatureHelp, DocFormat) { } } +TEST(SignatureHelp, TemplateArguments) { + std::string Top = R"cpp( + template bool foo(char); + template bool foo(float); + )cpp"; + + auto First = signatures(Top + "bool x = foo<^"); + EXPECT_THAT( + First.signatures, + UnorderedElementsAre(Sig("foo<[[typename T]], [[int]]>() -> bool"), + Sig("foo<[[int I]], [[int]]>() -> bool"))); + EXPECT_EQ(First.activeParameter, 0); + + auto Second = signatures(Top + "bool x = foo<1, ^"); + EXPECT_THAT(Second.signatures, + ElementsAre(Sig("foo<[[int I]], [[int]]>() -> bool"))); + EXPECT_EQ(Second.activeParameter, 1); +} + } // namespace } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/unittests/CompileCommandsTests.cpp b/clang-tools-extra/clangd/unittests/CompileCommandsTests.cpp index 4cb6ef9a16613..3afcf59ac0776 100644 --- a/clang-tools-extra/clangd/unittests/CompileCommandsTests.cpp +++ b/clang-tools-extra/clangd/unittests/CompileCommandsTests.cpp @@ -53,6 +53,18 @@ TEST(CommandMangler, Everything) { "foo.cc")); } +TEST(CommandMangler, FilenameMismatch) { + auto Mangler = CommandMangler::forTests(); + Mangler.ClangPath = testPath("clang"); + // Our compile flags refer to foo.cc... + std::vector Cmd = {"clang", "foo.cc"}; + // but we're applying it to foo.h... + Mangler.adjust(Cmd, "foo.h"); + // so transferCompileCommand should add -x c++-header to preserve semantics. + EXPECT_THAT( + Cmd, ElementsAre(testPath("clang"), "-x", "c++-header", "--", "foo.h")); +} + TEST(CommandMangler, ResourceDir) { auto Mangler = CommandMangler::forTests(); Mangler.ResourceDir = testPath("fake/resources"); diff --git a/clang-tools-extra/clangd/unittests/ConfigCompileTests.cpp b/clang-tools-extra/clangd/unittests/ConfigCompileTests.cpp index 87d8b9d976f0f..661784256af80 100644 --- a/clang-tools-extra/clangd/unittests/ConfigCompileTests.cpp +++ b/clang-tools-extra/clangd/unittests/ConfigCompileTests.cpp @@ -121,14 +121,15 @@ TEST_F(ConfigCompileTests, Condition) { } TEST_F(ConfigCompileTests, CompileCommands) { + Frag.CompileFlags.Compiler.emplace("tpc.exe"); Frag.CompileFlags.Add.emplace_back("-foo"); Frag.CompileFlags.Remove.emplace_back("--include-directory="); std::vector Argv = {"clang", "-I", "bar/", "--", "a.cc"}; EXPECT_TRUE(compileAndApply()); - EXPECT_THAT(Conf.CompileFlags.Edits, SizeIs(2)); + EXPECT_THAT(Conf.CompileFlags.Edits, SizeIs(3)); for (auto &Edit : Conf.CompileFlags.Edits) Edit(Argv); - EXPECT_THAT(Argv, ElementsAre("clang", "-foo", "--", "a.cc")); + EXPECT_THAT(Argv, ElementsAre("tpc.exe", "-foo", "--", "a.cc")); } TEST_F(ConfigCompileTests, CompilationDatabase) { diff --git a/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp b/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp index 5cf68c9c8e9c8..3d4bc1cea87c0 100644 --- a/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp +++ b/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp @@ -517,6 +517,80 @@ TEST(DiagnosticTest, ClangTidyWarningAsError) { DiagSeverity(DiagnosticsEngine::Error))))); } +TidyProvider addClangArgs(std::vector ExtraArgs) { + return [ExtraArgs = std::move(ExtraArgs)](tidy::ClangTidyOptions &Opts, + llvm::StringRef) { + if (!Opts.ExtraArgs) + Opts.ExtraArgs.emplace(); + for (llvm::StringRef Arg : ExtraArgs) + Opts.ExtraArgs->emplace_back(Arg); + }; +} + +TEST(DiagnosticTest, ClangTidyEnablesClangWarning) { + Annotations Main(R"cpp( // error-ok + static void [[foo]]() {} + )cpp"); + TestTU TU = TestTU::withCode(Main.code()); + // This is always emitted as a clang warning, not a clang-tidy diagnostic. + auto UnusedFooWarning = + AllOf(Diag(Main.range(), "unused function 'foo'"), + DiagName("-Wunused-function"), DiagSource(Diag::Clang), + DiagSeverity(DiagnosticsEngine::Warning)); + + // Check the -Wunused warning isn't initially on. + EXPECT_THAT(*TU.build().getDiagnostics(), IsEmpty()); + + // We enable warnings based on clang-tidy extra args. + TU.ClangTidyProvider = addClangArgs({"-Wunused"}); + EXPECT_THAT(*TU.build().getDiagnostics(), ElementsAre(UnusedFooWarning)); + + // But we don't respect other args. + TU.ClangTidyProvider = addClangArgs({"-Wunused", "-Dfoo=bar"}); + EXPECT_THAT(*TU.build().getDiagnostics(), ElementsAre(UnusedFooWarning)) + << "Not unused function 'bar'!"; + + // -Werror doesn't apply to warnings enabled by clang-tidy extra args. + TU.ExtraArgs = {"-Werror"}; + TU.ClangTidyProvider = addClangArgs({"-Wunused"}); + EXPECT_THAT(*TU.build().getDiagnostics(), + ElementsAre(DiagSeverity(DiagnosticsEngine::Warning))); + + // But clang-tidy extra args won't *downgrade* errors to warnings either. + TU.ExtraArgs = {"-Wunused", "-Werror"}; + TU.ClangTidyProvider = addClangArgs({"-Wunused"}); + EXPECT_THAT(*TU.build().getDiagnostics(), + ElementsAre(DiagSeverity(DiagnosticsEngine::Error))); + + // FIXME: we're erroneously downgrading the whole group, this should be Error. + TU.ExtraArgs = {"-Wunused-function", "-Werror"}; + TU.ClangTidyProvider = addClangArgs({"-Wunused"}); + EXPECT_THAT(*TU.build().getDiagnostics(), + ElementsAre(DiagSeverity(DiagnosticsEngine::Warning))); + + // This looks silly, but it's the typical result if a warning is enabled by a + // high-level .clang-tidy file and disabled by a low-level one. + TU.ExtraArgs = {}; + TU.ClangTidyProvider = addClangArgs({"-Wunused", "-Wno-unused"}); + EXPECT_THAT(*TU.build().getDiagnostics(), IsEmpty()); + + // Overriding only works in the proper order. + TU.ClangTidyProvider = addClangArgs({"-Wno-unused", "-Wunused"}); + EXPECT_THAT(*TU.build().getDiagnostics(), SizeIs(1)); + + // More specific vs less-specific: match clang behavior + TU.ClangTidyProvider = addClangArgs({"-Wunused", "-Wno-unused-function"}); + EXPECT_THAT(*TU.build().getDiagnostics(), IsEmpty()); + TU.ClangTidyProvider = addClangArgs({"-Wunused-function", "-Wno-unused"}); + EXPECT_THAT(*TU.build().getDiagnostics(), IsEmpty()); + + // We do allow clang-tidy config to disable warnings from the compile command. + // It's unclear this is ideal, but it's hard to avoid. + TU.ExtraArgs = {"-Wunused"}; + TU.ClangTidyProvider = addClangArgs({"-Wno-unused"}); + EXPECT_THAT(*TU.build().getDiagnostics(), IsEmpty()); +} + TEST(DiagnosticTest, LongFixMessages) { // We limit the size of printed code. Annotations Source(R"cpp( diff --git a/clang-tools-extra/clangd/unittests/HeadersTests.cpp b/clang-tools-extra/clangd/unittests/HeadersTests.cpp index 22caa59e33202..738a2fc18b2df 100644 --- a/clang-tools-extra/clangd/unittests/HeadersTests.cpp +++ b/clang-tools-extra/clangd/unittests/HeadersTests.cpp @@ -20,6 +20,7 @@ #include "llvm/Support/Error.h" #include "llvm/Support/FormatVariadic.h" #include "llvm/Support/Path.h" +#include "llvm/Support/ScopedPrinter.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -408,6 +409,58 @@ void foo(); EXPECT_FALSE(Includes.isSelfContained(getID("pp_depend.h", Includes))); } +TEST(StdlibTest, All) { + auto VectorH = stdlib::Header::named(""); + EXPECT_TRUE(VectorH); + EXPECT_EQ(llvm::to_string(*VectorH), ""); + EXPECT_FALSE(stdlib::Header::named("HeadersTests.cpp")); + + auto Vector = stdlib::Symbol::named("std::", "vector"); + EXPECT_TRUE(Vector); + EXPECT_EQ(llvm::to_string(*Vector), "std::vector"); + EXPECT_FALSE(stdlib::Symbol::named("std::", "dongle")); + EXPECT_FALSE(stdlib::Symbol::named("clang::", "ASTContext")); + + EXPECT_EQ(Vector->header(), *VectorH); + EXPECT_THAT(Vector->headers(), ElementsAre(*VectorH)); +} + +TEST(StdlibTest, Recognizer) { + auto TU = TestTU::withCode(R"cpp( + namespace std { + inline namespace inl { + + template + struct vector { class nested {}; }; + + class secret {}; + + } // inl + } // std + + class vector {}; + std::vector vec; + std::vector::nested nest; + std::secret sec; + )cpp"); + + auto AST = TU.build(); + auto &vector_nonstd = findDecl(AST, "vector"); + auto *vec = + cast(findDecl(AST, "vec")).getType()->getAsCXXRecordDecl(); + auto *nest = + cast(findDecl(AST, "nest")).getType()->getAsCXXRecordDecl(); + auto *sec = + cast(findDecl(AST, "sec")).getType()->getAsCXXRecordDecl(); + + stdlib::Recognizer recognizer; + + EXPECT_EQ(recognizer(&vector_nonstd), llvm::None); + EXPECT_EQ(recognizer(vec), stdlib::Symbol::named("std::", "vector")); + EXPECT_EQ(recognizer(nest), stdlib::Symbol::named("std::", "vector")); + EXPECT_EQ(recognizer(sec), llvm::None); +} + } // namespace } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp b/clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp index 12bcc9440f2de..b7792ca6f90da 100644 --- a/clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp +++ b/clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp @@ -8,7 +8,9 @@ #include "Annotations.h" #include "IncludeCleaner.h" +#include "SourceCode.h" #include "TestTU.h" +#include "llvm/ADT/ScopeExit.h" #include "llvm/Testing/Support/SupportHelpers.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -18,7 +20,9 @@ namespace clangd { namespace { using ::testing::ElementsAre; +using ::testing::ElementsAreArray; using ::testing::IsEmpty; +using ::testing::Pointee; using ::testing::UnorderedElementsAre; std::string guard(llvm::StringRef Code) { @@ -211,7 +215,7 @@ TEST(IncludeCleaner, ReferencedLocations) { auto AST = TU.build(); std::vector Points; - for (const auto &Loc : findReferencedLocations(AST)) { + for (const auto &Loc : findReferencedLocations(AST).User) { if (AST.getSourceManager().getBufferName(Loc).endswith( TU.HeaderFilename)) { Points.push_back(offsetToPosition( @@ -225,6 +229,82 @@ TEST(IncludeCleaner, ReferencedLocations) { } } +TEST(IncludeCleaner, Stdlib) { + // Smoke tests only for finding used symbols/headers. + // Details of Decl -> stdlib::Symbol -> stdlib::Headers mapping tested there. + auto TU = TestTU::withHeaderCode(R"cpp( + namespace std { class error_code {}; } + class error_code {}; + namespace nonstd { class error_code {}; } + )cpp"); + struct { + llvm::StringRef Code; + std::vector Symbols; + std::vector Headers; + } Tests[] = { + {"std::error_code x;", {"std::error_code"}, {""}}, + {"error_code x;", {}, {}}, + {"nonstd::error_code x;", {}, {}}, + }; + + for (const auto &Test : Tests) { + TU.Code = Test.Code.str(); + ParsedAST AST = TU.build(); + std::vector WantSyms; + for (const auto &SymName : Test.Symbols) { + auto QName = splitQualifiedName(SymName); + auto Sym = stdlib::Symbol::named(QName.first, QName.second); + EXPECT_TRUE(Sym) << SymName; + WantSyms.push_back(*Sym); + } + std::vector WantHeaders; + for (const auto &HeaderName : Test.Headers) { + auto Header = stdlib::Header::named(HeaderName); + EXPECT_TRUE(Header) << HeaderName; + WantHeaders.push_back(*Header); + } + + ReferencedLocations Locs = findReferencedLocations(AST); + EXPECT_THAT(Locs.Stdlib, ElementsAreArray(WantSyms)); + ReferencedFiles Files = findReferencedFiles(Locs, AST.getIncludeStructure(), + AST.getSourceManager()); + EXPECT_THAT(Files.Stdlib, ElementsAreArray(WantHeaders)); + } +} + +MATCHER_P(WrittenInclusion, Written, "") { + if (arg.Written != Written) + *result_listener << arg.Written; + return arg.Written == Written; +} + +TEST(IncludeCleaner, StdlibUnused) { + setIncludeCleanerAnalyzesStdlib(true); + auto Cleanup = + llvm::make_scope_exit([] { setIncludeCleanerAnalyzesStdlib(false); }); + + auto TU = TestTU::withCode(R"cpp( + #include + #include + std::list x; + )cpp"); + // Layout of std library impl is not relevant. + TU.AdditionalFiles["bits"] = R"cpp( + #pragma once + namespace std { + template class list {}; + template class queue {}; + } + )cpp"; + TU.AdditionalFiles["list"] = "#include "; + TU.AdditionalFiles["queue"] = "#include "; + TU.ExtraArgs = {"-isystem", testRoot()}; + auto AST = TU.build(); + + auto Unused = computeUnusedIncludes(AST); + EXPECT_THAT(Unused, ElementsAre(Pointee(WrittenInclusion("")))); +} + TEST(IncludeCleaner, GetUnusedHeaders) { llvm::StringLiteral MainFile = R"cpp( #include "a.h" @@ -301,7 +381,7 @@ TEST(IncludeCleaner, VirtualBuffers) { auto ReferencedFiles = findReferencedFiles(findReferencedLocations(AST), Includes, SM); llvm::StringSet<> ReferencedFileNames; - for (FileID FID : ReferencedFiles) + for (FileID FID : ReferencedFiles.User) ReferencedFileNames.insert( SM.getPresumedLoc(SM.getLocForStartOfFile(FID)).getFilename()); // Note we deduped the names as _number_ of s is uninteresting. @@ -352,7 +432,7 @@ TEST(IncludeCleaner, DistinctUnguardedInclusions) { AST.getIncludeStructure(), AST.getSourceManager()); llvm::StringSet<> ReferencedFileNames; auto &SM = AST.getSourceManager(); - for (FileID FID : ReferencedFiles) + for (FileID FID : ReferencedFiles.User) ReferencedFileNames.insert( SM.getPresumedLoc(SM.getLocForStartOfFile(FID)).getFilename()); // Note that we have uplifted the referenced files from non self-contained @@ -386,7 +466,7 @@ TEST(IncludeCleaner, NonSelfContainedHeaders) { AST.getIncludeStructure(), AST.getSourceManager()); llvm::StringSet<> ReferencedFileNames; auto &SM = AST.getSourceManager(); - for (FileID FID : ReferencedFiles) + for (FileID FID : ReferencedFiles.User) ReferencedFileNames.insert( SM.getPresumedLoc(SM.getLocForStartOfFile(FID)).getFilename()); // Note that we have uplifted the referenced files from non self-contained @@ -406,7 +486,7 @@ TEST(IncludeCleaner, IWYUPragmas) { auto ReferencedFiles = findReferencedFiles(findReferencedLocations(AST), AST.getIncludeStructure(), AST.getSourceManager()); - EXPECT_TRUE(ReferencedFiles.empty()); + EXPECT_TRUE(ReferencedFiles.User.empty()); EXPECT_THAT(AST.getDiagnostics(), llvm::ValueIs(IsEmpty())); } diff --git a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp index b6c6dd17dbe3b..e8880981c1a47 100644 --- a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp +++ b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp @@ -23,20 +23,23 @@ std::ostream &operator<<(std::ostream &Stream, const InlayHint &Hint) { namespace { -using ::testing::UnorderedElementsAre; +using ::testing::ElementsAre; std::vector hintsOfKind(ParsedAST &AST, InlayHintKind Kind) { std::vector Result; - for (auto &Hint : inlayHints(AST)) { + for (auto &Hint : inlayHints(AST, /*RestrictRange=*/llvm::None)) { if (Hint.kind == Kind) Result.push_back(Hint); } return Result; } +enum HintSide { Left, Right }; + struct ExpectedHint { std::string Label; std::string RangeName; + HintSide Side = Left; friend std::ostream &operator<<(std::ostream &Stream, const ExpectedHint &Hint) { @@ -46,9 +49,13 @@ struct ExpectedHint { MATCHER_P2(HintMatcher, Expected, Code, "") { return arg.label == Expected.Label && - arg.range == Code.range(Expected.RangeName); + arg.range == Code.range(Expected.RangeName) && + arg.position == + ((Expected.Side == Left) ? arg.range.start : arg.range.end); } +MATCHER_P(labelIs, Label, "") { return arg.label == Label; } + template void assertHints(InlayHintKind Kind, llvm::StringRef AnnotatedSource, ExpectedHints... Expected) { @@ -58,18 +65,23 @@ void assertHints(InlayHintKind Kind, llvm::StringRef AnnotatedSource, auto AST = TU.build(); EXPECT_THAT(hintsOfKind(AST, Kind), - UnorderedElementsAre(HintMatcher(Expected, Source)...)); + ElementsAre(HintMatcher(Expected, Source)...)); } +// Hack to allow expression-statements operating on parameter packs in C++14. +template void ignore(T &&...) {} + template void assertParameterHints(llvm::StringRef AnnotatedSource, ExpectedHints... Expected) { + ignore(Expected.Side = Left...); assertHints(InlayHintKind::ParameterHint, AnnotatedSource, Expected...); } template void assertTypeHints(llvm::StringRef AnnotatedSource, ExpectedHints... Expected) { + ignore(Expected.Side = Right...); assertHints(InlayHintKind::TypeHint, AnnotatedSource, Expected...); } @@ -631,6 +643,18 @@ TEST(TypeHints, Deduplication) { ExpectedHint{": int", "var"}); } +TEST(InlayHints, RestrictRange) { + Annotations Code(R"cpp( + auto a = false; + [[auto b = 1; + auto c = '2';]] + auto d = 3.f; + )cpp"); + auto AST = TestTU::withCode(Code.code()).build(); + EXPECT_THAT(inlayHints(AST, Code.range()), + ElementsAre(labelIs(": int"), labelIs(": char"))); +} + // FIXME: Low-hanging fruit where we could omit a type hint: // - auto x = TypeName(...); // - auto x = (TypeName) (...); diff --git a/clang-tools-extra/clangd/unittests/InsertionPointTests.cpp b/clang-tools-extra/clangd/unittests/InsertionPointTests.cpp new file mode 100644 index 0000000000000..2a2756a703efa --- /dev/null +++ b/clang-tools-extra/clangd/unittests/InsertionPointTests.cpp @@ -0,0 +1,210 @@ +//===-- InsertionPointTess.cpp -------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "Annotations.h" +#include "Protocol.h" +#include "SourceCode.h" +#include "TestTU.h" +#include "TestWorkspace.h" +#include "XRefs.h" +#include "refactor/InsertionPoint.h" +#include "clang/AST/DeclBase.h" +#include "llvm/Testing/Support/Error.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace clang { +namespace clangd { +namespace { +using llvm::HasValue; + +TEST(InsertionPointTests, Generic) { + Annotations Code(R"cpp( + namespace ns { + $a^int a1; + $b^// leading comment + int b; + $c^int c1; // trailing comment + int c2; + $a2^int a2; + $end^}; + )cpp"); + + auto StartsWith = + [&](llvm::StringLiteral S) -> std::function { + return [S](const Decl *D) { + if (const auto *ND = llvm::dyn_cast(D)) + return llvm::StringRef(ND->getNameAsString()).startswith(S); + return false; + }; + }; + + auto AST = TestTU::withCode(Code.code()).build(); + auto &NS = cast(findDecl(AST, "ns")); + + // Test single anchors. + auto Point = [&](llvm::StringLiteral Prefix, Anchor::Dir Direction) { + auto Loc = insertionPoint(NS, {Anchor{StartsWith(Prefix), Direction}}); + return sourceLocToPosition(AST.getSourceManager(), Loc); + }; + EXPECT_EQ(Point("a", Anchor::Above), Code.point("a")); + EXPECT_EQ(Point("a", Anchor::Below), Code.point("b")); + EXPECT_EQ(Point("b", Anchor::Above), Code.point("b")); + EXPECT_EQ(Point("b", Anchor::Below), Code.point("c")); + EXPECT_EQ(Point("c", Anchor::Above), Code.point("c")); + EXPECT_EQ(Point("c", Anchor::Below), Code.point("a2")); + EXPECT_EQ(Point("", Anchor::Above), Code.point("a")); + EXPECT_EQ(Point("", Anchor::Below), Code.point("end")); + EXPECT_EQ(Point("no_match", Anchor::Below), Position{}); + + // Test anchor chaining. + auto Chain = [&](llvm::StringLiteral P1, llvm::StringLiteral P2) { + auto Loc = insertionPoint(NS, {Anchor{StartsWith(P1), Anchor::Above}, + Anchor{StartsWith(P2), Anchor::Above}}); + return sourceLocToPosition(AST.getSourceManager(), Loc); + }; + EXPECT_EQ(Chain("a", "b"), Code.point("a")); + EXPECT_EQ(Chain("b", "a"), Code.point("b")); + EXPECT_EQ(Chain("no_match", "a"), Code.point("a")); + + // Test edit generation. + auto Edit = insertDecl("foo;", NS, {Anchor{StartsWith("a"), Anchor::Below}}); + ASSERT_THAT_EXPECTED(Edit, llvm::Succeeded()); + EXPECT_EQ(offsetToPosition(Code.code(), Edit->getOffset()), Code.point("b")); + EXPECT_EQ(Edit->getReplacementText(), "foo;"); + // If no match, the edit is inserted at the end. + Edit = insertDecl("x;", NS, {Anchor{StartsWith("no_match"), Anchor::Below}}); + ASSERT_THAT_EXPECTED(Edit, llvm::Succeeded()); + EXPECT_EQ(offsetToPosition(Code.code(), Edit->getOffset()), + Code.point("end")); +} + +// For CXX, we should check: +// - special handling for access specifiers +// - unwrapping of template decls +TEST(InsertionPointTests, CXX) { + Annotations Code(R"cpp( + class C { + public: + $Method^void pubMethod(); + $Field^int PubField; + + $private^private: + $field^int PrivField; + $method^void privMethod(); + template void privTemplateMethod(); + $end^}; + )cpp"); + + auto AST = TestTU::withCode(Code.code()).build(); + const CXXRecordDecl &C = cast(findDecl(AST, "C")); + + auto IsMethod = [](const Decl *D) { return llvm::isa(D); }; + auto Any = [](const Decl *D) { return true; }; + + // Test single anchors. + auto Point = [&](Anchor A, AccessSpecifier Protection) { + auto Loc = insertionPoint(C, {A}, Protection); + return sourceLocToPosition(AST.getSourceManager(), Loc); + }; + EXPECT_EQ(Point({IsMethod, Anchor::Above}, AS_public), Code.point("Method")); + EXPECT_EQ(Point({IsMethod, Anchor::Below}, AS_public), Code.point("Field")); + EXPECT_EQ(Point({Any, Anchor::Above}, AS_public), Code.point("Method")); + EXPECT_EQ(Point({Any, Anchor::Below}, AS_public), Code.point("private")); + EXPECT_EQ(Point({IsMethod, Anchor::Above}, AS_private), Code.point("method")); + EXPECT_EQ(Point({IsMethod, Anchor::Below}, AS_private), Code.point("end")); + EXPECT_EQ(Point({Any, Anchor::Above}, AS_private), Code.point("field")); + EXPECT_EQ(Point({Any, Anchor::Below}, AS_private), Code.point("end")); + EXPECT_EQ(Point({IsMethod, Anchor::Above}, AS_protected), Position{}); + EXPECT_EQ(Point({IsMethod, Anchor::Below}, AS_protected), Position{}); + EXPECT_EQ(Point({Any, Anchor::Above}, AS_protected), Position{}); + EXPECT_EQ(Point({Any, Anchor::Below}, AS_protected), Position{}); + + // Edits when there's no match --> end of matching access control section. + auto Edit = insertDecl("x", C, {}, AS_public); + ASSERT_THAT_EXPECTED(Edit, llvm::Succeeded()); + EXPECT_EQ(offsetToPosition(Code.code(), Edit->getOffset()), + Code.point("private")); + + Edit = insertDecl("x", C, {}, AS_private); + ASSERT_THAT_EXPECTED(Edit, llvm::Succeeded()); + EXPECT_EQ(offsetToPosition(Code.code(), Edit->getOffset()), + Code.point("end")); + + Edit = insertDecl("x", C, {}, AS_protected); + ASSERT_THAT_EXPECTED(Edit, llvm::Succeeded()); + EXPECT_EQ(offsetToPosition(Code.code(), Edit->getOffset()), + Code.point("end")); + EXPECT_EQ(Edit->getReplacementText(), "protected:\nx"); +} + +MATCHER_P(replacementText, Text, "") { + if (arg.getReplacementText() != Text) { + *result_listener << "replacement is " << arg.getReplacementText().str(); + return false; + } + return true; +} + +TEST(InsertionPointTests, CXXAccessProtection) { + // Empty class uses default access. + auto AST = TestTU::withCode("struct S{};").build(); + const CXXRecordDecl &S = cast(findDecl(AST, "S")); + ASSERT_THAT_EXPECTED(insertDecl("x", S, {}, AS_public), + HasValue(replacementText("x"))); + ASSERT_THAT_EXPECTED(insertDecl("x", S, {}, AS_private), + HasValue(replacementText("private:\nx"))); + + // We won't insert above the first access specifier if there's nothing there. + AST = TestTU::withCode("struct T{private:};").build(); + const CXXRecordDecl &T = cast(findDecl(AST, "T")); + ASSERT_THAT_EXPECTED(insertDecl("x", T, {}, AS_public), + HasValue(replacementText("public:\nx"))); + ASSERT_THAT_EXPECTED(insertDecl("x", T, {}, AS_private), + HasValue(replacementText("x"))); + + // But we will if there are declarations. + AST = TestTU::withCode("struct U{int i;private:};").build(); + const CXXRecordDecl &U = cast(findDecl(AST, "U")); + ASSERT_THAT_EXPECTED(insertDecl("x", U, {}, AS_public), + HasValue(replacementText("x"))); + ASSERT_THAT_EXPECTED(insertDecl("x", U, {}, AS_private), + HasValue(replacementText("x"))); +} + +// In ObjC we need to take care to get the @end fallback right. +TEST(InsertionPointTests, ObjC) { + Annotations Code(R"objc( + @interface Foo + -(void) v; + $endIface^@end + @implementation Foo + -(void) v {} + $endImpl^@end + )objc"); + auto TU = TestTU::withCode(Code.code()); + TU.Filename = "TestTU.m"; + auto AST = TU.build(); + + auto &Impl = + cast(findDecl(AST, [&](const NamedDecl &D) { + return llvm::isa(D); + })); + auto &Iface = *Impl.getClassInterface(); + Anchor End{[](const Decl *) { return true; }, Anchor::Below}; + + const auto &SM = AST.getSourceManager(); + EXPECT_EQ(sourceLocToPosition(SM, insertionPoint(Iface, {End})), + Code.point("endIface")); + EXPECT_EQ(sourceLocToPosition(SM, insertionPoint(Impl, {End})), + Code.point("endImpl")); +} + +} // namespace +} // namespace clangd +} // namespace clang diff --git a/clang-tools-extra/clangd/unittests/QualityTests.cpp b/clang-tools-extra/clangd/unittests/QualityTests.cpp index f5fcb0e8d04ed..2992f694637d3 100644 --- a/clang-tools-extra/clangd/unittests/QualityTests.cpp +++ b/clang-tools-extra/clangd/unittests/QualityTests.cpp @@ -54,13 +54,6 @@ TEST(QualityTests, SymbolQualitySignalExtraction) { auto AST = Header.build(); SymbolQualitySignals Quality; - Quality.merge(findSymbol(Symbols, "_X")); - EXPECT_FALSE(Quality.Deprecated); - EXPECT_FALSE(Quality.ImplementationDetail); - EXPECT_TRUE(Quality.ReservedName); - EXPECT_EQ(Quality.References, SymbolQualitySignals().References); - EXPECT_EQ(Quality.Category, SymbolQualitySignals::Variable); - Quality.merge(findSymbol(Symbols, "X_Y_Decl")); EXPECT_TRUE(Quality.ImplementationDetail); @@ -83,6 +76,16 @@ TEST(QualityTests, SymbolQualitySignalExtraction) { Quality = {}; Quality.merge(CodeCompletionResult("if")); EXPECT_EQ(Quality.Category, SymbolQualitySignals::Keyword); + + // Testing ReservedName in main file, we don't index those symbols in headers. + auto MainAST = TestTU::withCode("int _X;").build(); + SymbolSlab MainSymbols = std::get<0>(indexMainDecls(MainAST)); + + Quality = {}; + Quality.merge(findSymbol(MainSymbols, "_X")); + EXPECT_FALSE(Quality.Deprecated); + EXPECT_FALSE(Quality.ImplementationDetail); + EXPECT_TRUE(Quality.ReservedName); } TEST(QualityTests, SymbolRelevanceSignalExtraction) { diff --git a/clang-tools-extra/clangd/unittests/SelectionTests.cpp b/clang-tools-extra/clangd/unittests/SelectionTests.cpp index 6c6782a097db5..9da111f684c31 100644 --- a/clang-tools-extra/clangd/unittests/SelectionTests.cpp +++ b/clang-tools-extra/clangd/unittests/SelectionTests.cpp @@ -204,9 +204,12 @@ TEST(SelectionTest, CommonAncestor) { { R"cpp( struct S { S(const char*); }; - S [[s ^= "foo"]]; + [[S s ^= "foo"]]; )cpp", - "CXXConstructExpr", + // The AST says a CXXConstructExpr covers the = sign in C++14. + // But we consider CXXConstructExpr to only own brackets. + // (It's not the interesting constructor anyway, just S(&&)). + "VarDecl", }, { R"cpp( @@ -231,7 +234,7 @@ TEST(SelectionTest, CommonAncestor) { R"cpp( [[void (^*S)(int)]] = nullptr; )cpp", - "FunctionProtoTypeLoc", + "PointerTypeLoc", }, { R"cpp( @@ -243,7 +246,7 @@ TEST(SelectionTest, CommonAncestor) { R"cpp( [[void ^(*S)(int)]] = nullptr; )cpp", - "FunctionProtoTypeLoc", + "ParenTypeLoc", }, { R"cpp( @@ -318,6 +321,16 @@ TEST(SelectionTest, CommonAncestor) { {"[[st^ruct {int x;}]] y;", "CXXRecordDecl"}, {"[[struct {int x;} ^y]];", "VarDecl"}, {"struct {[[int ^x]];} y;", "FieldDecl"}, + + // Tricky case: nested ArrayTypeLocs have the same token range. + {"const int x = 1, y = 2; int array[^[[x]]][10][y];", "DeclRefExpr"}, + {"const int x = 1, y = 2; int array[x][10][^[[y]]];", "DeclRefExpr"}, + {"const int x = 1, y = 2; int array[x][^[[10]]][y];", "IntegerLiteral"}, + {"const int x = 1, y = 2; [[i^nt]] array[x][10][y];", "BuiltinTypeLoc"}, + {"void func(int x) { int v_array[^[[x]]][10]; }", "DeclRefExpr"}, + + {"int (*getFunc([[do^uble]]))(int);", "BuiltinTypeLoc"}, + // FIXME: the AST has no location info for qualifiers. {"const [[a^uto]] x = 42;", "AutoTypeLoc"}, {"[[co^nst auto x = 42]];", "VarDecl"}, @@ -377,6 +390,7 @@ TEST(SelectionTest, CommonAncestor) { decltype([[^a]] + a) b; )cpp", "DeclRefExpr"}, + {"[[decltype]]^(1) b;", "DecltypeTypeLoc"}, // Not the VarDecl. // Objective-C nullability attributes. { diff --git a/clang-tools-extra/clangd/unittests/SourceCodeTests.cpp b/clang-tools-extra/clangd/unittests/SourceCodeTests.cpp index bfe0e430b775a..fdcee9feffb2b 100644 --- a/clang-tools-extra/clangd/unittests/SourceCodeTests.cpp +++ b/clang-tools-extra/clangd/unittests/SourceCodeTests.cpp @@ -315,6 +315,16 @@ TEST(SourceCodeTests, SourceLocationInMainFile) { } } +TEST(SourceCodeTests, isReservedName) { + EXPECT_FALSE(isReservedName("")); + EXPECT_FALSE(isReservedName("_")); + EXPECT_FALSE(isReservedName("foo")); + EXPECT_FALSE(isReservedName("_foo")); + EXPECT_TRUE(isReservedName("__foo")); + EXPECT_TRUE(isReservedName("_Foo")); + EXPECT_FALSE(isReservedName("foo__bar")) << "FIXME"; +} + TEST(SourceCodeTests, CollectIdentifiers) { auto Style = format::getLLVMStyle(); auto IDs = collectIdentifiers(R"cpp( diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp index 5afaa83bfdcbc..24cd8b7313bd3 100644 --- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp +++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp @@ -17,7 +17,6 @@ #include "clang/Index/IndexingOptions.h" #include "clang/Tooling/Tooling.h" #include "llvm/ADT/IntrusiveRefCntPtr.h" -#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/VirtualFileSystem.h" @@ -1849,6 +1848,22 @@ TEST_F(SymbolCollectorTest, NoCrashOnObjCMethodCStyleParam) { UnorderedElementsAre(QName("Foo"), QName("Foo::fun:"))); } +TEST_F(SymbolCollectorTest, Reserved) { + const char *Header = R"cpp( + void __foo(); + namespace _X { int secret; } + )cpp"; + + CollectorOpts.CollectReserved = true; + runSymbolCollector("", Header); + EXPECT_THAT(Symbols, UnorderedElementsAre(QName("__foo"), QName("_X"), + QName("_X::secret"))); + + CollectorOpts.CollectReserved = false; + runSymbolCollector("", Header); // + EXPECT_THAT(Symbols, IsEmpty()); +} + } // namespace } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst index 6f8a25e0d04f5..03a52fa7c7aec 100644 --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -67,12 +67,18 @@ The improvements are... Improvements to clang-tidy -------------------------- +- Ignore warnings from macros defined in system headers, if not using the + `-system-headers` flag. + - Added support for globbing in `NOLINT*` expressions, to simplify suppressing multiple warnings in the same line. - Added support for `NOLINTBEGIN` ... `NOLINTEND` comments to suppress Clang-Tidy warnings over multiple lines. +- Generalized the `modernize-use-default-member-init` check to handle non-default + constructors. + New checks ^^^^^^^^^^ @@ -148,7 +154,7 @@ Changes in existing checks - Fixed a false positive in :doc:`fuchsia-trailing-return ` for C++17 deduction guides. - + - Fixed a false positive in :doc:`bugprone-throw-keyword-missing ` when creating an exception object using placement new diff --git a/clang-tools-extra/docs/clang-doc.rst b/clang-tools-extra/docs/clang-doc.rst index 9be8a8dc31d45..2db3e914ac8a7 100644 --- a/clang-tools-extra/docs/clang-doc.rst +++ b/clang-tools-extra/docs/clang-doc.rst @@ -12,7 +12,7 @@ source code and comments. The tool is in a very early development stage, so you might encounter bugs and crashes. Submitting reports with information about how to reproduce the issue -to `the LLVM bugtracker `_ will definitely help the +to `the LLVM bug tracker `_ will definitely help the project. If you have any ideas or suggestions, please to put a feature request there. diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst index 1e6936f9cbdf9..8d0a568cff881 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/list.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -116,13 +116,12 @@ Clang-Tidy Checks `cert-dcl50-cpp `_, `cert-dcl58-cpp `_, `cert-env33-c `_, + `cert-err33-c `_, `cert-err34-c `_, `cert-err52-cpp `_, `cert-err58-cpp `_, `cert-err60-cpp `_, - `cert-exp42-c `_, `cert-flp30-c `_, - `cert-flp37-c `_, `cert-mem57-cpp `_, `cert-msc50-cpp `_, `cert-msc51-cpp `_, @@ -213,7 +212,7 @@ Clang-Tidy Checks `llvmlibc-implementation-in-namespace `_, `llvmlibc-restrict-system-libc-headers `_, "Yes" `misc-definitions-in-headers `_, "Yes" - `misc-misleading-identifier `_, + `misc-misleading-identifier `_, `misc-misplaced-const `_, `misc-new-delete-overloads `_, `misc-no-recursion `_, @@ -260,8 +259,8 @@ Clang-Tidy Checks `modernize-use-using `_, "Yes" `mpi-buffer-deref `_, "Yes" `mpi-type-mismatch `_, "Yes" - `objc-avoid-nserror-init `_, `objc-assert-equals `_, "Yes" + `objc-avoid-nserror-init `_, `objc-dealloc-in-category `_, `objc-forbidden-subclassing `_, `objc-missing-hash `_, @@ -283,16 +282,16 @@ Clang-Tidy Checks `performance-noexcept-move-constructor `_, "Yes" `performance-trivially-destructible `_, "Yes" `performance-type-promotion-in-math-fn `_, "Yes" - `performance-unnecessary-copy-initialization `_, + `performance-unnecessary-copy-initialization `_, "Yes" `performance-unnecessary-value-param `_, "Yes" `portability-restrict-system-includes `_, "Yes" `portability-simd-intrinsics `_, - `readability-avoid-const-params-in-decls `_, + `readability-avoid-const-params-in-decls `_, "Yes" `readability-braces-around-statements `_, "Yes" `readability-const-return-type `_, "Yes" `readability-container-data-pointer `_, "Yes" `readability-container-size-empty `_, "Yes" - `readability-convert-member-functions-to-static `_, + `readability-convert-member-functions-to-static `_, "Yes" `readability-delete-null-pointer `_, "Yes" `readability-else-after-return `_, "Yes" `readability-function-cognitive-complexity `_, @@ -338,13 +337,14 @@ Clang-Tidy Checks `cert-dcl03-c `_, `misc-static-assert `_, "Yes" `cert-dcl16-c `_, `readability-uppercase-literal-suffix `_, "Yes" `cert-dcl37-c `_, `bugprone-reserved-identifier `_, "Yes" - `cert-err33-c `_, `bugprone-unused-return-value `_, `cert-dcl51-cpp `_, `bugprone-reserved-identifier `_, "Yes" `cert-dcl54-cpp `_, `misc-new-delete-overloads `_, `cert-dcl59-cpp `_, `google-build-namespaces `_, `cert-err09-cpp `_, `misc-throw-by-value-catch-by-reference `_, `cert-err61-cpp `_, `misc-throw-by-value-catch-by-reference `_, + `cert-exp42-c `_, `bugprone-suspicious-memory-comparison `_, `cert-fio38-c `_, `misc-non-copyable-objects `_, + `cert-flp37-c `_, `bugprone-suspicious-memory-comparison `_, `cert-msc30-c `_, `cert-msc50-cpp `_, `cert-msc32-c `_, `cert-msc51-cpp `_, `cert-oop11-cpp `_, `performance-move-constructor-init `_, diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize-use-default-member-init.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize-use-default-member-init.rst index a77415301ef6a..2d3ed38014937 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/modernize-use-default-member-init.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/modernize-use-default-member-init.rst @@ -3,7 +3,7 @@ modernize-use-default-member-init ================================= -This check converts a default constructor's member initializers into the new +This check converts constructors' member initializers into the new default member initializers in C++11. Other member initializers that match the default member initializer are removed. This can reduce repeated code or allow use of '= default'. diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize-pass-by-value.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize-pass-by-value.cpp index 28e4014c43d36..2aacbdd1c7a6a 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/modernize-pass-by-value.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize-pass-by-value.cpp @@ -246,3 +246,20 @@ struct V { V::V(const Movable &M) : M(M) {} // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: pass by value and use std::move // CHECK-FIXES: V::V(const Movable &M) : M(M) {} + +// Test with paired lvalue/rvalue overloads. +struct W1 { + W1(const Movable &M) : M(M) {} + W1(Movable &&M); + Movable M; +}; +struct W2 { + W2(const Movable &M, int) : M(M) {} + W2(Movable &&M, int); + Movable M; +}; +struct W3 { + W3(const W1 &, const Movable &M) : M(M) {} + W3(W1 &&, Movable &&M); + Movable M; +}; diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize-use-default-member-init.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize-use-default-member-init.cpp index 27c947820d9c9..464cfbdeacb88 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/modernize-use-default-member-init.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize-use-default-member-init.cpp @@ -45,6 +45,42 @@ struct PositiveInt { // CHECK-FIXES: int j{1}; }; +struct PositiveNotDefaultInt { + PositiveNotDefaultInt(int) : i(7) {} + // CHECK-FIXES: PositiveNotDefaultInt(int) {} + int i; + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use default member initializer for 'i' + // CHECK-FIXES: int i{7}; +}; + +// We cannot reconcile these initializers. +struct TwoConstructors { + TwoConstructors(int) : i(7) {} + TwoConstructors(int, int) : i(8) {} + int i; +}; + +struct PositiveNotDefaultOOLInt { + PositiveNotDefaultOOLInt(int); + int i; + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use default member initializer for 'i' + // CHECK-FIXES: int i{7}; +}; + +PositiveNotDefaultOOLInt::PositiveNotDefaultOOLInt(int) : i(7) {} +// CHECK-FIXES: PositiveNotDefaultOOLInt::PositiveNotDefaultOOLInt(int) {} + +struct PositiveNotDefaultOOLInt2 { + PositiveNotDefaultOOLInt2(int, int); + int i; + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use default member initializer for 'i' + // CHECK-FIXES: int i{7}; + int j; +}; + +PositiveNotDefaultOOLInt2::PositiveNotDefaultOOLInt2(int, int arg) : i(7), j(arg) {} +// CHECK-FIXES: PositiveNotDefaultOOLInt2::PositiveNotDefaultOOLInt2(int, int arg) : j(arg) {} + struct PositiveUnaryMinusInt { PositiveUnaryMinusInt() : j(-1) {} // CHECK-FIXES: PositiveUnaryMinusInt() {} @@ -234,12 +270,6 @@ struct NegativeBitField int i : 5; }; -struct NegativeNotDefaultInt -{ - NegativeNotDefaultInt(int) : i(7) {} - int i; -}; - struct NegativeDefaultArg { NegativeDefaultArg(int i = 4) : i(i) {} diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/file-filter/system/system-header.h b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/file-filter/system/system-header.h index 98482c4277dda..6e338c85f394e 100644 --- a/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/file-filter/system/system-header.h +++ b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/file-filter/system/system-header.h @@ -1 +1,3 @@ class A0 { A0(int); }; + +#define TO_FLOAT_PTR(x) ((float *)(x)) diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/file-filter.cpp b/clang-tools-extra/test/clang-tidy/infrastructure/file-filter.cpp index 9ee5cad979f6a..a7498723de2bf 100644 --- a/clang-tools-extra/test/clang-tidy/infrastructure/file-filter.cpp +++ b/clang-tools-extra/test/clang-tidy/infrastructure/file-filter.cpp @@ -9,6 +9,8 @@ // file-filter\header*.h due to code order between '/' and '\\'. // RUN: clang-tidy -checks='-*,google-explicit-constructor' -header-filter='.*' -system-headers %s -- -I %S/Inputs/file-filter/system/.. -isystem %S/Inputs/file-filter/system 2>&1 | FileCheck --check-prefix=CHECK4 %s // RUN: clang-tidy -checks='-*,google-explicit-constructor' -header-filter='.*' -system-headers -quiet %s -- -I %S/Inputs/file-filter/system/.. -isystem %S/Inputs/file-filter/system 2>&1 | FileCheck --check-prefix=CHECK4-QUIET %s +// RUN: clang-tidy -checks='-*,cppcoreguidelines-pro-type-cstyle-cast' -header-filter='.*' -system-headers %s -- -I %S/Inputs/file-filter/system/.. -isystem %S/Inputs/file-filter/system 2>&1 | FileCheck --check-prefix=CHECK5 %s +// RUN: clang-tidy -checks='-*,cppcoreguidelines-pro-type-cstyle-cast' -header-filter='.*' %s -- -I %S/Inputs/file-filter/system/.. -isystem %S/Inputs/file-filter/system 2>&1 | FileCheck --check-prefix=CHECK5-NO-SYSTEM-HEADERS %s #include "header1.h" // CHECK-NOT: warning: @@ -71,3 +73,8 @@ class A { A(int); }; // CHECK4-NOT: Suppressed {{.*}} warnings // CHECK4-NOT: Use -header-filter=.* {{.*}} // CHECK4-QUIET-NOT: Suppressed + +int x = 123; +auto x_ptr = TO_FLOAT_PTR(&x); +// CHECK5: :[[@LINE-1]]:14: warning: do not use C-style cast to convert between unrelated types +// CHECK5-NO-SYSTEM-HEADERS-NOT: :[[@LINE-2]]:14: warning: do not use C-style cast to convert between unrelated types diff --git a/clang/CMakeLists.txt b/clang/CMakeLists.txt index 049bdfdbb5cd8..2092c85231eb4 100644 --- a/clang/CMakeLists.txt +++ b/clang/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.13.4) # If we are not building as a part of LLVM, build Clang as an # standalone project, using LLVM as an external library: -if( CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR ) +if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) project(Clang) set(CMAKE_CXX_STANDARD 14 CACHE STRING "C++ standard to conform to") @@ -10,7 +10,7 @@ if( CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR ) set(CMAKE_CXX_EXTENSIONS NO) # Rely on llvm-config. - set(CONFIG_OUTPUT) + set(LLVM_CONFIG_OUTPUT) if(LLVM_CONFIG) set (LLVM_CONFIG_FOUND 1) message(STATUS "Found LLVM_CONFIG as ${LLVM_CONFIG}") @@ -20,35 +20,36 @@ if( CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR ) automatically, but you can also use LLVM_DIR to specify \ the path containing LLVMConfig.cmake.") set(CONFIG_COMMAND ${LLVM_CONFIG} - "--assertion-mode" - "--bindir" - "--libdir" "--includedir" "--prefix" "--src-root" - "--cmakedir") + "--cmakedir" + "--bindir" + "--libdir" + "--assertion-mode" + ) execute_process( COMMAND ${CONFIG_COMMAND} RESULT_VARIABLE HAD_ERROR - OUTPUT_VARIABLE CONFIG_OUTPUT + OUTPUT_VARIABLE LLVM_CONFIG_OUTPUT ) if(NOT HAD_ERROR) string(REGEX REPLACE "[ \t]*[\r\n]+[ \t]*" ";" - CONFIG_OUTPUT ${CONFIG_OUTPUT}) + LLVM_CONFIG_OUTPUT ${LLVM_CONFIG_OUTPUT}) else() string(REPLACE ";" " " CONFIG_COMMAND_STR "${CONFIG_COMMAND}") message(STATUS "${CONFIG_COMMAND_STR}") message(FATAL_ERROR "llvm-config failed with status ${HAD_ERROR}") endif() - list(GET CONFIG_OUTPUT 0 ENABLE_ASSERTIONS) - list(GET CONFIG_OUTPUT 1 TOOLS_BINARY_DIR) - list(GET CONFIG_OUTPUT 2 LIBRARY_DIR) - list(GET CONFIG_OUTPUT 3 INCLUDE_DIR) - list(GET CONFIG_OUTPUT 4 LLVM_OBJ_ROOT) - list(GET CONFIG_OUTPUT 5 MAIN_SRC_DIR) - list(GET CONFIG_OUTPUT 6 LLVM_CONFIG_CMAKE_DIR) + list(GET LLVM_CONFIG_OUTPUT 0 MAIN_INCLUDE_DIR) + list(GET LLVM_CONFIG_OUTPUT 1 LLVM_OBJ_ROOT) + list(GET LLVM_CONFIG_OUTPUT 2 MAIN_SRC_DIR) + list(GET LLVM_CONFIG_OUTPUT 3 LLVM_CONFIG_CMAKE_DIR) + list(GET LLVM_CONFIG_OUTPUT 4 TOOLS_BINARY_DIR) + list(GET LLVM_CONFIG_OUTPUT 5 LIBRARY_DIR) + list(GET LLVM_CONFIG_OUTPUT 6 ENABLE_ASSERTIONS) # Normalize LLVM_CMAKE_DIR. --cmakedir might contain backslashes. # CMake assumes slashes as PATH. @@ -71,17 +72,17 @@ if( CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR ) if (NOT LLVM_CONFIG_FOUND) # Pull values from LLVMConfig.cmake. We can drop this once the llvm-config # path is removed. + set(MAIN_INCLUDE_DIR ${LLVM_INCLUDE_DIR}) + set(LLVM_OBJ_DIR ${LLVM_BINARY_DIR}) set(TOOLS_BINARY_DIR ${LLVM_TOOLS_BINARY_DIR}) set(LIBRARY_DIR ${LLVM_LIBRARY_DIR}) - set(INCLUDE_DIR ${LLVM_INCLUDE_DIR}) - set(LLVM_OBJ_DIR ${LLVM_BINARY_DIR}) endif() - set(LLVM_TOOLS_BINARY_DIR ${TOOLS_BINARY_DIR} CACHE PATH "Path to llvm/bin") - set(LLVM_LIBRARY_DIR ${LIBRARY_DIR} CACHE PATH "Path to llvm/lib") - set(LLVM_MAIN_INCLUDE_DIR ${INCLUDE_DIR} CACHE PATH "Path to llvm/include") + set(LLVM_MAIN_INCLUDE_DIR ${MAIN_INCLUDE_DIR} CACHE PATH "Path to llvm/include") set(LLVM_BINARY_DIR ${LLVM_OBJ_ROOT} CACHE PATH "Path to LLVM build tree") set(LLVM_MAIN_SRC_DIR ${MAIN_SRC_DIR} CACHE PATH "Path to LLVM source tree") + set(LLVM_TOOLS_BINARY_DIR ${TOOLS_BINARY_DIR} CACHE PATH "Path to llvm/bin") + set(LLVM_LIBRARY_DIR ${LIBRARY_DIR} CACHE PATH "Path to llvm/lib") find_program(LLVM_TABLEGEN_EXE "llvm-tblgen" ${LLVM_TOOLS_BINARY_DIR} NO_DEFAULT_PATH) @@ -184,15 +185,23 @@ if( CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR ) endif() endif() - set( CLANG_BUILT_STANDALONE 1 ) + set(CLANG_BUILT_STANDALONE TRUE) + set(BACKEND_PACKAGE_STRING "LLVM ${LLVM_PACKAGE_VERSION}") else() set(BACKEND_PACKAGE_STRING "${PACKAGE_STRING}") +endif() # standalone + +if(NOT DEFINED LLVM_COMMON_CMAKE_UTILS) + set(LLVM_COMMON_CMAKE_UTILS ${CMAKE_CURRENT_SOURCE_DIR}/../cmake) endif() # Make sure that our source directory is on the current cmake module path so that # we can include cmake files from this directory. -list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules") +list(INSERT CMAKE_MODULE_PATH 0 + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules" + "${LLVM_COMMON_CMAKE_UTILS}/Modules" + ) if(LLVM_ENABLE_LIBXML2) # Don't look for libxml if we're using MSan, since uninstrumented third party @@ -228,9 +237,6 @@ set(CLANG_SPAWN_CC1 OFF CACHE BOOL "Whether clang should use a new process for the CC1 invocation") option(CLANG_DEFAULT_PIE_ON_LINUX "Default to -fPIE and -pie on Linux" OFF) -if(CLANG_DEFAULT_PIE_ON_LINUX) - set(CLANG_DEFAULT_PIE_ON_LINUX 1) -endif() # TODO: verify the values against LangStandards.def? set(CLANG_DEFAULT_STD_C "" CACHE STRING diff --git a/clang/cmake/modules/CMakeLists.txt b/clang/cmake/modules/CMakeLists.txt index 561665d58cad9..3890ea14d06c6 100644 --- a/clang/cmake/modules/CMakeLists.txt +++ b/clang/cmake/modules/CMakeLists.txt @@ -1,4 +1,5 @@ include(LLVMDistributionSupport) +include(FindPrefixFromConfig) # Generate a list of CMake library targets so that other CMake projects can # link against them. LLVM calls its version of this file LLVMExports.cmake, but @@ -29,16 +30,7 @@ set(CLANG_CONFIG_CMAKE_DIR) set(CLANG_CONFIG_LLVM_CMAKE_DIR) # Generate ClangConfig.cmake for the install tree. -set(CLANG_CONFIG_CODE " -# Compute the installation prefix from this LLVMConfig.cmake file location. -get_filename_component(CLANG_INSTALL_PREFIX \"\${CMAKE_CURRENT_LIST_FILE}\" PATH)") -# Construct the proper number of get_filename_component(... PATH) -# calls to compute the installation prefix. -string(REGEX REPLACE "/" ";" _count "${CLANG_INSTALL_PACKAGE_DIR}") -foreach(p ${_count}) - set(CLANG_CONFIG_CODE "${CLANG_CONFIG_CODE} -get_filename_component(CLANG_INSTALL_PREFIX \"\${CLANG_INSTALL_PREFIX}\" PATH)") -endforeach(p) +find_prefix_from_config(CLANG_CONFIG_CODE CLANG_INSTALL_PREFIX "${CLANG_INSTALL_PACKAGE_DIR}") set(CLANG_CONFIG_CMAKE_DIR "\${CLANG_INSTALL_PREFIX}/${CLANG_INSTALL_PACKAGE_DIR}") set(CLANG_CONFIG_LLVM_CMAKE_DIR "\${CLANG_INSTALL_PREFIX}/${LLVM_INSTALL_PACKAGE_DIR}") get_config_exports_includes(Clang CLANG_CONFIG_INCLUDE_EXPORTS) diff --git a/clang/docs/ClangCommandLineReference.rst b/clang/docs/ClangCommandLineReference.rst index 97807009fd911..72d571dd10ee3 100644 --- a/clang/docs/ClangCommandLineReference.rst +++ b/clang/docs/ClangCommandLineReference.rst @@ -216,6 +216,8 @@ Trivial automatic variable initialization to zero is only here for benchmarks, i .. option:: -faligned-new= +.. option:: -fautomatic + .. option:: -ffixed-r19 Reserve register r19 (Hexagon only) @@ -242,6 +244,10 @@ Specify comma-separated list of triples OpenMP offloading targets to be supporte .. option:: -force\_load .. program:: clang +.. option:: -fplugin-arg-- + +Pass to plugin + .. option:: -framework .. option:: -frtlib-add-rpath, -fno-rtlib-add-rpath @@ -265,7 +271,7 @@ Build this module as a system module. Only used with -emit-module Method to generate ID's for compilation units for single source offloading languages CUDA and HIP: 'hash' (ID's generated by hashing file path and command line options) \| 'random' (ID's generated as random numbers) \| 'none' (disabled). Default is 'hash'. This option will be overridden by option '-cuid=\[ID\]' if it is specified. -.. option:: --gcc-toolchain=, -gcc-toolchain +.. option:: --gcc-toolchain= Search for GCC installation in the specified directory on targets which commonly use GCC. The directory usually contains 'lib{,32,64}/gcc{,-cross}/$triple' and 'include'. If specified, sysroot is skipped for GCC detection. Note: executables (e.g. ld) used by the compiler are not overridden by the selected GCC installation @@ -395,6 +401,10 @@ Do not add include paths for CUDA/HIP and do not include the default CUDA/HIP wr Do not link device library for CUDA/HIP device compilation +.. option:: -nohipwrapperinc + +Do not include the default HIP wrapper headers and include paths + .. option:: -nolibc .. option:: -nomultidefs @@ -423,6 +433,10 @@ Disable standard #include directories for the C++ standard library Write output to +.. option:: -objcmt-allowlist-dir-path=, -objcmt-white-list-dir-path=, -objcmt-whitelist-dir-path= + +Only modify files with a filename contained in the provided directory path + .. option:: -objcmt-atomic-property Make migration to 'atomic' properties @@ -483,16 +497,20 @@ Enable migration to use NS\_NONATOMIC\_IOSONLY macro for setting property's 'ato Enable migration to annotate property with NS\_RETURNS\_INNER\_POINTER -.. option:: -objcmpt-allowlist-dir-path=, -objcmt-whitelist-dir-path=, -objcmt-white-list-dir-path= +.. option:: -object -Only modify files with a filename contained in the provided directory path +.. option:: -object-file-name=, -object-file-name -.. option:: -object +Set the output for debug infos .. option:: --offload-arch=, --cuda-gpu-arch=, --no-offload-arch= CUDA offloading device architecture (e.g. sm\_35), or HIP offloading target ID in the form of a device architecture followed by target ID features delimited by a colon. Each target ID feature is a pre-defined string followed by a plus or minus sign (e.g. gfx908:xnack+:sramecc-). May be specified more than once. +.. option:: --offload=,... + +Specify comma-separated list of offloading target triples (HIP only) + .. option:: -p, --profile .. option:: -pagezero\_size @@ -901,13 +919,17 @@ Level of field padding for AddressSanitizer Enable linker dead stripping of globals in AddressSanitizer +.. option:: -fsanitize-address-outline-instrumentation, -fno-sanitize-address-outline-instrumentation + +Always generate function calls for address sanitizer instrumentation + .. option:: -fsanitize-address-poison-custom-array-cookie, -fno-sanitize-address-poison-custom-array-cookie Enable poisoning array cookies when using custom operator new\[\] in AddressSanitizer .. option:: -fsanitize-address-use-after-return= -Select the mode of detecting stack use-after-return in AddressSanitizer +Select the mode of detecting stack use-after-return in AddressSanitizer: never \| runtime (default) \| always .. option:: -fsanitize-address-use-after-scope, -fno-sanitize-address-use-after-scope @@ -1060,10 +1082,6 @@ Pass the comma separated arguments in to the preprocessor Pass to the preprocessor -.. option:: -fmacro-prefix-map= - -remap file source paths in predefined preprocessor macros - Include path management ----------------------- @@ -1335,12 +1353,12 @@ Enable the specified warning Enable warnings for deprecated constructs and define \_\_DEPRECATED +.. option:: -Wframe-larger-than=, -Wframe-larger-than + .. option:: -Wnonportable-cfstrings, -Wno-nonportable-cfstrings Target-independent compilation options ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. option:: -Wframe-larger-than= - .. option:: -fPIC, -fno-PIC .. option:: -fPIE, -fno-PIE @@ -1357,6 +1375,10 @@ Emit an address-significance table .. option:: -falign-functions= .. program:: clang +.. option:: -falign-loops= + +N must be a power of two. Align loops to the boundary + .. program:: clang1 .. option:: -faligned-allocation, -faligned-new, -fno-aligned-allocation .. program:: clang @@ -1371,6 +1393,10 @@ Treat editor placeholders as valid source code .. option:: -faltivec, -fno-altivec +.. option:: -faltivec-src-compat= + +Source-level compatibility for Altivec vectors (for PowerPC targets). This includes results of vector comparison (scalar for 'xl', vector for 'gcc') as well as behavior when initializing with a scalar (splatting for 'xl', element zero only for 'gcc'). For 'mixed', the compatibility is as 'gcc' for 'vector bool/vector pixel' and as 'xl' for other types. Current default is 'mixed'. + .. option:: -fansi-escape-codes Use ANSI escape codes for diagnostics @@ -1391,6 +1417,10 @@ Enable Apple gcc-compatible #pragma pack handling Restrict code to those available for App Extensions +.. option:: -fapprox-func, -fno-approx-func + +Allow certain math function calls to be replaced with an approximately equivalent calculation + .. option:: -fasm, -fno-asm .. option:: -fasm-blocks, -fno-asm-blocks @@ -1523,6 +1553,8 @@ Enable C++ exceptions .. option:: -fcxx-modules, -fno-cxx-modules +Enable modules for C++ + .. option:: -fdata-sections, -fno-data-sections Place each data in its own section @@ -1677,7 +1709,7 @@ The compilation directory to embed in the debug info and coverage mapping. .. option:: -ffile-prefix-map= -remap file source paths in debug info and predefined preprocessor macros +remap file source paths in debug info, predefined preprocessor macros and \_\_builtin\_FILE() .. option:: -ffinite-loops, -fno-finite-loops @@ -1705,7 +1737,7 @@ Enable support for int128\_t type .. option:: -ffp-contract= -Form fused FP ops (e.g. FMAs): fast (fuses across statements disregarding pragmas) \| on (only fuses in the same statement unless dictated by pragmas) \| off (never fuses) \| fast-honor-pragmas (fuses across statements unless dictated by pragmas). Default is 'fast' for CUDA, 'fast-honor-pragmas' for HIP, and 'on' otherwise. +Form fused FP ops (e.g. FMAs): fast (fuses across statements disregarding pragmas) \| on (only fuses in the same statement unless dictated by pragmas) \| off (never fuses) \| fast-honor-pragmas (fuses across statements unless diectated by pragmas). Default is 'fast' for CUDA, 'fast-honor-pragmas' for HIP, and 'on' otherwise. .. option:: -ffp-exception-behavior= @@ -1819,29 +1851,19 @@ Enable implicit vector bit-casts .. option:: -flimited-precision= -.. option:: -flto, -fno-lto - -Enable LTO in 'full' mode - .. option:: -flto-jobs= Controls the backend parallelism of -flto=thin (default of 0 means the number of threads will be derived from the number of CPUs detected) -.. program:: clang1 -.. option:: -flto= -.. program:: clang +.. option:: -flto=, -flto (equivalent to -flto=full), -flto=auto (equivalent to -flto=full), -flto=jobserver (equivalent to -flto=full) Set LTO mode to either 'full' or 'thin' -.. program:: clang2 -.. option:: -flto=auto -.. program:: clang +.. option:: -fmacro-backtrace-limit= -.. program:: clang3 -.. option:: -flto=jobserver -.. program:: clang +.. option:: -fmacro-prefix-map= -.. option:: -fmacro-backtrace-limit= +remap file source paths in predefined preprocessor macros and \_\_builtin\_FILE() .. option:: -fmath-errno, -fno-math-errno @@ -1873,6 +1895,10 @@ Allow merging of constants Format message diagnostics so that they fit within N columns +.. option:: -fminimize-whitespace, -fno-minimize-whitespace + +Minimize whitespace when emitting preprocessor output + .. option:: -fmodule-file-deps, -fno-module-file-deps .. option:: -fmodule-map-file= @@ -2017,13 +2043,7 @@ Specify the target Objective-C runtime kind and version Enable ARC-style weak references in Objective-C -.. option:: -foffload-lto, -fno-offload-lto - -Enable LTO in 'full' mode for offload compilation - -.. program:: clang1 -.. option:: -foffload-lto= -.. program:: clang +.. option:: -foffload-lto=, -foffload-lto (equivalent to -foffload-lto=full) Set LTO mode to either 'full' or 'thin' for offload compilation @@ -2033,16 +2053,25 @@ Set LTO mode to either 'full' or 'thin' for offload compilation Parse OpenMP pragmas and generate parallel code. +.. option:: -fopenmp-extensions, -fno-openmp-extensions + +Enable all Clang extensions for OpenMP directives and clauses + .. option:: -fopenmp-simd, -fno-openmp-simd Emit OpenMP code only for SIMD-based constructs. -.. option:: -fopenmp-version= +.. option:: -fopenmp-target-debug, -fno-openmp-target-debug -.. option:: -fopenmp-extensions, -fno-openmp-extensions +Enable debugging in the OpenMP offloading device RTL + +.. option:: -fopenmp-target-new-runtime, -fno-openmp-target-new-runtime + +Use the new bitcode library for OpenMP offloading + +.. option:: -fopenmp-version= -Enable or disable all Clang extensions for OpenMP directives and clauses. By -default, they are enabled. +Set OpenMP version (e.g. 45 for OpenMP 4.5, 50 for OpenMP 5.0). Default value is 50. .. program:: clang1 .. option:: -fopenmp= @@ -2209,6 +2238,10 @@ Set update method of profile counters (atomic,prefer-atomic,single) Use instrumentation data for profile-guided optimization. If pathname is a directory, it reads from /default.profdata. Otherwise, it reads from file . +.. option:: -fprotect-parens, -fno-protect-parens + +Determines whether the optimizer honors parentheses when floating-point expressions are evaluated + .. option:: -fpseudo-probe-for-profiling, -fno-pseudo-probe-for-profiling Emit pseudo probes for sample profiling @@ -2377,6 +2410,10 @@ Enable optimizations based on the strict rules for overwriting polymorphic C++ o .. option:: -fstruct-path-tbaa, -fno-struct-path-tbaa +.. option:: -fswift-async-fp=