diff --git a/clang-tools-extra/clang-query/Query.cpp b/clang-tools-extra/clang-query/Query.cpp index e33612a2e16d021f9b2c1852be62d6c96b152fd3..5cf24dbb58a7771867302f953654799534efec94 100644 --- a/clang-tools-extra/clang-query/Query.cpp +++ b/clang-tools-extra/clang-query/Query.cpp @@ -48,8 +48,6 @@ bool HelpQuery::run(llvm::raw_ostream &OS, QuerySession &QS) const { " AsIs " "Print and match the AST as clang sees it. This mode is the " "default.\n" - " IgnoreImplicitCastsAndParentheses " - "Omit implicit casts and parens in matching and dumping.\n" " IgnoreUnlessSpelledInSource " "Omit AST nodes unless spelled in the source.\n" " set output " diff --git a/clang-tools-extra/clang-query/tool/ClangQuery.cpp b/clang-tools-extra/clang-query/tool/ClangQuery.cpp index 45a35507394513d52c114610db808960c9e83642..2bdb36192f350eaa8cb7f810e2ac5fb3ff1fbbcb 100644 --- a/clang-tools-extra/clang-query/tool/ClangQuery.cpp +++ b/clang-tools-extra/clang-query/tool/ClangQuery.cpp @@ -33,10 +33,10 @@ #include "clang/Tooling/Tooling.h" #include "llvm/LineEditor/LineEditor.h" #include "llvm/Support/CommandLine.h" +#include "llvm/Support/Error.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Signals.h" #include "llvm/Support/WithColor.h" -#include #include using namespace clang; @@ -73,16 +73,15 @@ static cl::opt PreloadFile( bool runCommandsInFile(const char *ExeName, std::string const &FileName, QuerySession &QS) { - std::ifstream Input(FileName.c_str()); - if (!Input.is_open()) { - llvm::errs() << ExeName << ": cannot open " << FileName << "\n"; - return 1; + auto Buffer = llvm::MemoryBuffer::getFile(FileName); + if (!Buffer) { + llvm::errs() << ExeName << ": cannot open " << FileName << ": " + << Buffer.getError().message() << "\n"; + return true; } - std::string FileContent((std::istreambuf_iterator(Input)), - std::istreambuf_iterator()); + StringRef FileContentRef(Buffer.get()->getBuffer()); - StringRef FileContentRef(FileContent); while (!FileContentRef.empty()) { QueryRef Q = QueryParser::parse(FileContentRef, QS); if (!Q->run(llvm::outs(), QS)) diff --git a/clang-tools-extra/clang-tidy/ClangTidy.cpp b/clang-tools-extra/clang-tidy/ClangTidy.cpp index dc523d0731d5a0465e8d8033ffe9ae430343b3b0..88ba4bf63e09fd9bf0ea6e0c5f71340e01de4a2a 100644 --- a/clang-tools-extra/clang-tidy/ClangTidy.cpp +++ b/clang-tools-extra/clang-tidy/ClangTidy.cpp @@ -95,7 +95,7 @@ private: class ErrorReporter { public: - ErrorReporter(ClangTidyContext &Context, bool ApplyFixes, + ErrorReporter(ClangTidyContext &Context, FixBehaviour ApplyFixes, llvm::IntrusiveRefCntPtr BaseFS) : Files(FileSystemOptions(), std::move(BaseFS)), DiagOpts(new DiagnosticOptions()), @@ -133,8 +133,9 @@ public: auto Diag = Diags.Report(Loc, Diags.getCustomDiagID(Level, "%0 [%1]")) << Message.Message << Name; // FIXME: explore options to support interactive fix selection. - const llvm::StringMap *ChosenFix = selectFirstFix(Error); - if (ApplyFixes && ChosenFix) { + const llvm::StringMap *ChosenFix; + if (ApplyFixes != FB_NoFix && + (ChosenFix = getFixIt(Error, ApplyFixes == FB_FixNotes))) { for (const auto &FileAndReplacements : *ChosenFix) { for (const auto &Repl : FileAndReplacements.second) { ++TotalFixes; @@ -187,7 +188,7 @@ public: } void finish() { - if (ApplyFixes && TotalFixes > 0) { + if (TotalFixes > 0) { Rewriter Rewrite(SourceMgr, LangOpts); for (const auto &FileAndReplacements : FileReplacements) { StringRef File = FileAndReplacements.first(); @@ -287,7 +288,7 @@ private: SourceManager SourceMgr; llvm::StringMap FileReplacements; ClangTidyContext &Context; - bool ApplyFixes; + FixBehaviour ApplyFixes; unsigned TotalFixes; unsigned AppliedFixes; unsigned WarningsAsErrors; @@ -392,6 +393,10 @@ ClangTidyASTConsumerFactory::CreateASTConsumer( std::vector> Checks = CheckFactories->createChecks(&Context); + llvm::erase_if(Checks, [&](std::unique_ptr &Check) { + return !Check->isLanguageVersionSupported(Context.getLangOpts()); + }); + ast_matchers::MatchFinder::MatchFinderOptions FinderOptions; std::unique_ptr Profiling; @@ -415,8 +420,6 @@ ClangTidyASTConsumerFactory::CreateASTConsumer( } for (auto &Check : Checks) { - if (!Check->isLanguageVersionSupported(Context.getLangOpts())) - continue; Check->registerMatchers(&*Finder); Check->registerPPCallbacks(*SM, PP, ModuleExpanderPP); } @@ -500,7 +503,8 @@ runClangTidy(clang::tidy::ClangTidyContext &Context, const CompilationDatabase &Compilations, ArrayRef InputFiles, llvm::IntrusiveRefCntPtr BaseFS, - bool EnableCheckProfile, llvm::StringRef StoreCheckProfile) { + bool ApplyAnyFix, bool EnableCheckProfile, + llvm::StringRef StoreCheckProfile) { ClangTool Tool(Compilations, InputFiles, std::make_shared(), BaseFS); @@ -527,7 +531,7 @@ runClangTidy(clang::tidy::ClangTidyContext &Context, Context.setEnableProfiling(EnableCheckProfile); Context.setProfileStoragePrefix(StoreCheckProfile); - ClangTidyDiagnosticConsumer DiagConsumer(Context); + ClangTidyDiagnosticConsumer DiagConsumer(Context, nullptr, true, ApplyAnyFix); DiagnosticsEngine DE(new DiagnosticIDs(), new DiagnosticOptions(), &DiagConsumer, /*ShouldOwnClient=*/false); Context.setDiagnosticsEngine(&DE); @@ -574,7 +578,7 @@ runClangTidy(clang::tidy::ClangTidyContext &Context, } void handleErrors(llvm::ArrayRef Errors, - ClangTidyContext &Context, bool Fix, + ClangTidyContext &Context, FixBehaviour Fix, unsigned &WarningsAsErrorsCount, llvm::IntrusiveRefCntPtr BaseFS) { ErrorReporter Reporter(Context, Fix, std::move(BaseFS)); diff --git a/clang-tools-extra/clang-tidy/ClangTidy.h b/clang-tools-extra/clang-tidy/ClangTidy.h index 99dcbb6a269a8b7d84fbe03ab35fbfcbac693365..bbe4fe69123ff4d04286305085feb8434a4cff91 100644 --- a/clang-tools-extra/clang-tidy/ClangTidy.h +++ b/clang-tools-extra/clang-tidy/ClangTidy.h @@ -79,17 +79,28 @@ runClangTidy(clang::tidy::ClangTidyContext &Context, const tooling::CompilationDatabase &Compilations, ArrayRef InputFiles, llvm::IntrusiveRefCntPtr BaseFS, - bool EnableCheckProfile = false, + bool ApplyAnyFix, bool EnableCheckProfile = false, llvm::StringRef StoreCheckProfile = StringRef()); +/// Controls what kind of fixes clang-tidy is allowed to apply. +enum FixBehaviour { + /// Don't try to apply any fix. + FB_NoFix, + /// Only apply fixes added to warnings. + FB_Fix, + /// Apply fixes found in notes. + FB_FixNotes +}; + // FIXME: This interface will need to be significantly extended to be useful. // FIXME: Implement confidence levels for displaying/fixing errors. // -/// Displays the found \p Errors to the users. If \p Fix is true, \p -/// Errors containing fixes are automatically applied and reformatted. If no -/// clang-format configuration file is found, the given \P FormatStyle is used. +/// Displays the found \p Errors to the users. If \p Fix is \ref FB_Fix or \ref +/// FB_FixNotes, \p Errors containing fixes are automatically applied and +/// reformatted. If no clang-format configuration file is found, the given \P +/// FormatStyle is used. void handleErrors(llvm::ArrayRef Errors, - ClangTidyContext &Context, bool Fix, + ClangTidyContext &Context, FixBehaviour Fix, unsigned &WarningsAsErrorsCount, llvm::IntrusiveRefCntPtr BaseFS); diff --git a/clang-tools-extra/clang-tidy/ClangTidyCheck.cpp b/clang-tools-extra/clang-tidy/ClangTidyCheck.cpp index e1bea430a89a5ce9724514e05f474d9abccf8b2a..46b69ed538cb2ce4e37678253824968b9b15b40f 100644 --- a/clang-tools-extra/clang-tidy/ClangTidyCheck.cpp +++ b/clang-tools-extra/clang-tidy/ClangTidyCheck.cpp @@ -15,30 +15,6 @@ namespace clang { namespace tidy { -char MissingOptionError::ID; -char UnparseableEnumOptionError::ID; -char UnparseableIntegerOptionError::ID; - -std::string MissingOptionError::message() const { - llvm::SmallString<128> Buffer({"option not found '", OptionName, "'"}); - return std::string(Buffer); -} - -std::string UnparseableEnumOptionError::message() const { - llvm::SmallString<256> Buffer({"invalid configuration value '", LookupValue, - "' for option '", LookupName, "'"}); - if (SuggestedValue) - Buffer.append({"; did you mean '", *SuggestedValue, "'?"}); - return std::string(Buffer); -} - -std::string UnparseableIntegerOptionError::message() const { - llvm::SmallString<256> Buffer({"invalid configuration value '", LookupValue, - "' for option '", LookupName, "'; expected ", - (IsBoolean ? "a bool" : "an integer value")}); - return std::string(Buffer); -} - ClangTidyCheck::ClangTidyCheck(StringRef CheckName, ClangTidyContext *Context) : CheckName(CheckName), Context(Context), Options(CheckName, Context->getOptions().CheckOptions, Context) { @@ -75,12 +51,12 @@ ClangTidyCheck::OptionsView::OptionsView( : NamePrefix(CheckName.str() + "."), CheckOptions(CheckOptions), Context(Context) {} -llvm::Expected +llvm::Optional ClangTidyCheck::OptionsView::get(StringRef LocalName) const { const auto &Iter = CheckOptions.find(NamePrefix + LocalName.str()); if (Iter != CheckOptions.end()) return Iter->getValue().Value; - return llvm::make_error((NamePrefix + LocalName).str()); + return None; } static ClangTidyOptions::OptionMap::const_iterator @@ -97,16 +73,16 @@ findPriorityOption(const ClangTidyOptions::OptionMap &Options, StringRef NamePre return IterGlobal; } -llvm::Expected +llvm::Optional ClangTidyCheck::OptionsView::getLocalOrGlobal(StringRef LocalName) const { auto Iter = findPriorityOption(CheckOptions, NamePrefix, LocalName); if (Iter != CheckOptions.end()) return Iter->getValue().Value; - return llvm::make_error((NamePrefix + LocalName).str()); + return None; } -static llvm::Expected getAsBool(StringRef Value, - const llvm::Twine &LookupName) { +static Optional getAsBool(StringRef Value, + const llvm::Twine &LookupName) { if (llvm::Optional Parsed = llvm::yaml::parseBool(Value)) return *Parsed; @@ -115,46 +91,30 @@ static llvm::Expected getAsBool(StringRef Value, long long Number; if (!Value.getAsInteger(10, Number)) return Number != 0; - return llvm::make_error(LookupName.str(), - Value.str(), true); + return None; } template <> -llvm::Expected +llvm::Optional ClangTidyCheck::OptionsView::get(StringRef LocalName) const { - llvm::Expected ValueOr = get(LocalName); - if (ValueOr) - return getAsBool(*ValueOr, NamePrefix + LocalName); - return ValueOr.takeError(); -} - -template <> -bool ClangTidyCheck::OptionsView::get(StringRef LocalName, - bool Default) const { - llvm::Expected ValueOr = get(LocalName); - if (ValueOr) - return *ValueOr; - reportOptionParsingError(ValueOr.takeError()); - return Default; + if (llvm::Optional ValueOr = get(LocalName)) { + if (auto Result = getAsBool(*ValueOr, NamePrefix + LocalName)) + return Result; + diagnoseBadBooleanOption(NamePrefix + LocalName, *ValueOr); + } + return None; } template <> -llvm::Expected +llvm::Optional ClangTidyCheck::OptionsView::getLocalOrGlobal(StringRef LocalName) const { auto Iter = findPriorityOption(CheckOptions, NamePrefix, LocalName); - if (Iter != CheckOptions.end()) - return getAsBool(Iter->getValue().Value, Iter->getKey()); - return llvm::make_error((NamePrefix + LocalName).str()); -} - -template <> -bool ClangTidyCheck::OptionsView::getLocalOrGlobal(StringRef LocalName, - bool Default) const { - llvm::Expected ValueOr = getLocalOrGlobal(LocalName); - if (ValueOr) - return *ValueOr; - reportOptionParsingError(ValueOr.takeError()); - return Default; + if (Iter != CheckOptions.end()) { + if (auto Result = getAsBool(Iter->getValue().Value, Iter->getKey())) + return Result; + diagnoseBadBooleanOption(Iter->getKey(), Iter->getValue().Value); + } + return None; } void ClangTidyCheck::OptionsView::store(ClangTidyOptions::OptionMap &Options, @@ -176,14 +136,14 @@ void ClangTidyCheck::OptionsView::store( store(Options, LocalName, Value ? StringRef("true") : StringRef("false")); } -llvm::Expected ClangTidyCheck::OptionsView::getEnumInt( +llvm::Optional ClangTidyCheck::OptionsView::getEnumInt( StringRef LocalName, ArrayRef Mapping, bool CheckGlobal, bool IgnoreCase) const { auto Iter = CheckGlobal ? findPriorityOption(CheckOptions, NamePrefix, LocalName) : CheckOptions.find((NamePrefix + LocalName).str()); if (Iter == CheckOptions.end()) - return llvm::make_error((NamePrefix + LocalName).str()); + return None; StringRef Value = Iter->getValue().Value; StringRef Closest; @@ -206,39 +166,54 @@ llvm::Expected ClangTidyCheck::OptionsView::getEnumInt( } } if (EditDistance < 3) - return llvm::make_error( - Iter->getKey().str(), Iter->getValue().Value, Closest.str()); - return llvm::make_error(Iter->getKey().str(), - Iter->getValue().Value); + diagnoseBadEnumOption(Iter->getKey().str(), Iter->getValue().Value, + Closest); + else + diagnoseBadEnumOption(Iter->getKey().str(), Iter->getValue().Value); + return None; } -void ClangTidyCheck::OptionsView::reportOptionParsingError( - llvm::Error &&Err) const { - if (auto RemainingErrors = - llvm::handleErrors(std::move(Err), [](const MissingOptionError &) {})) - Context->configurationDiag(llvm::toString(std::move(RemainingErrors))); +static constexpr llvm::StringLiteral ConfigWarning( + "invalid configuration value '%0' for option '%1'%select{|; expected a " + "bool|; expected an integer|; did you mean '%3'?}2"); + +void ClangTidyCheck::OptionsView::diagnoseBadBooleanOption( + const Twine &Lookup, StringRef Unparsed) const { + SmallString<64> Buffer; + Context->configurationDiag(ConfigWarning) + << Unparsed << Lookup.toStringRef(Buffer) << 1; } -template <> -Optional ClangTidyCheck::OptionsView::getOptional( - StringRef LocalName) const { - if (auto ValueOr = get(LocalName)) - return *ValueOr; - else - consumeError(ValueOr.takeError()); - return llvm::None; +void ClangTidyCheck::OptionsView::diagnoseBadIntegerOption( + const Twine &Lookup, StringRef Unparsed) const { + SmallString<64> Buffer; + Context->configurationDiag(ConfigWarning) + << Unparsed << Lookup.toStringRef(Buffer) << 2; } -template <> -Optional -ClangTidyCheck::OptionsView::getOptionalLocalOrGlobal( - StringRef LocalName) const { - if (auto ValueOr = getLocalOrGlobal(LocalName)) - return *ValueOr; +void ClangTidyCheck::OptionsView::diagnoseBadEnumOption( + const Twine &Lookup, StringRef Unparsed, StringRef Suggestion) const { + SmallString<64> Buffer; + auto Diag = Context->configurationDiag(ConfigWarning) + << Unparsed << Lookup.toStringRef(Buffer); + if (Suggestion.empty()) + Diag << 0; else - consumeError(ValueOr.takeError()); - return llvm::None; + Diag << 3 << Suggestion; } +std::string ClangTidyCheck::OptionsView::get(StringRef LocalName, + StringRef Default) const { + if (llvm::Optional Val = get(LocalName)) + return std::move(*Val); + return Default.str(); +} +std::string +ClangTidyCheck::OptionsView::getLocalOrGlobal(StringRef LocalName, + StringRef Default) const { + if (llvm::Optional Val = getLocalOrGlobal(LocalName)) + return std::move(*Val); + return Default.str(); +} } // namespace tidy } // namespace clang diff --git a/clang-tools-extra/clang-tidy/ClangTidyCheck.h b/clang-tools-extra/clang-tidy/ClangTidyCheck.h index 9fa0d63286409102bfa22f3c8c8986790e4ac010..20e9b8e47e6f4f761cd9e434df24069cf175068b 100644 --- a/clang-tools-extra/clang-tidy/ClangTidyCheck.h +++ b/clang-tools-extra/clang-tidy/ClangTidyCheck.h @@ -14,7 +14,6 @@ #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/Basic/Diagnostic.h" #include "llvm/ADT/Optional.h" -#include "llvm/Support/Error.h" #include #include #include @@ -33,65 +32,6 @@ template struct OptionEnumMapping { static ArrayRef> getEnumMapping() = delete; }; -template class OptionError : public llvm::ErrorInfo { - std::error_code convertToErrorCode() const override { - return llvm::inconvertibleErrorCode(); - } - -public: - void log(raw_ostream &OS) const override { OS << this->message(); } -}; - -class MissingOptionError : public OptionError { -public: - explicit MissingOptionError(std::string OptionName) - : OptionName(OptionName) {} - - std::string message() const override; - static char ID; -private: - const std::string OptionName; -}; - -class UnparseableEnumOptionError - : public OptionError { -public: - explicit UnparseableEnumOptionError(std::string LookupName, - std::string LookupValue) - : LookupName(LookupName), LookupValue(LookupValue) {} - explicit UnparseableEnumOptionError(std::string LookupName, - std::string LookupValue, - std::string SuggestedValue) - : LookupName(LookupName), LookupValue(LookupValue), - SuggestedValue(SuggestedValue) {} - - std::string message() const override; - static char ID; - -private: - const std::string LookupName; - const std::string LookupValue; - const llvm::Optional SuggestedValue; -}; - -class UnparseableIntegerOptionError - : public OptionError { -public: - explicit UnparseableIntegerOptionError(std::string LookupName, - std::string LookupValue, - bool IsBoolean = false) - : LookupName(LookupName), LookupValue(LookupValue), IsBoolean(IsBoolean) { - } - - std::string message() const override; - static char ID; - -private: - const std::string LookupName; - const std::string LookupValue; - const bool IsBoolean; -}; - /// Base class for all clang-tidy checks. /// /// To implement a ``ClangTidyCheck``, write a subclass and override some of the @@ -198,6 +138,13 @@ public: /// Methods of this class prepend ``CheckName + "."`` to translate check-local /// option names to global option names. class OptionsView { + void diagnoseBadIntegerOption(const Twine &Lookup, + StringRef Unparsed) const; + void diagnoseBadBooleanOption(const Twine &Lookup, + StringRef Unparsed) const; + void diagnoseBadEnumOption(const Twine &Lookup, StringRef Unparsed, + StringRef Suggestion = StringRef()) const; + public: /// Initializes the instance using \p CheckName + "." as a prefix. OptionsView(StringRef CheckName, @@ -207,30 +154,24 @@ public: /// Read a named option from the ``Context``. /// /// Reads the option with the check-local name \p LocalName from the - /// ``CheckOptions``. If the corresponding key is not present, returns - /// a ``MissingOptionError``. - llvm::Expected get(StringRef LocalName) const; + /// ``CheckOptions``. If the corresponding key is not present, return + /// ``None``. + llvm::Optional get(StringRef LocalName) const; /// Read a named option from the ``Context``. /// /// Reads the option with the check-local name \p LocalName from the /// ``CheckOptions``. If the corresponding key is not present, returns /// \p Default. - std::string get(StringRef LocalName, StringRef Default) const { - if (llvm::Expected Val = get(LocalName)) - return *Val; - else - llvm::consumeError(Val.takeError()); - return Default.str(); - } + std::string get(StringRef LocalName, StringRef Default) const; /// Read a named option from the ``Context``. /// /// Reads the option with the check-local name \p LocalName from local or /// global ``CheckOptions``. Gets local option first. If local is not /// present, falls back to get global option. If global option is not - /// present either, returns a ``MissingOptionError``. - llvm::Expected getLocalOrGlobal(StringRef LocalName) const; + /// present either, return ``None``. + llvm::Optional getLocalOrGlobal(StringRef LocalName) const; /// Read a named option from the ``Context``. /// @@ -238,48 +179,42 @@ public: /// global ``CheckOptions``. Gets local option first. If local is not /// present, falls back to get global option. If global option is not /// present either, returns \p Default. - std::string getLocalOrGlobal(StringRef LocalName, StringRef Default) const { - if (llvm::Expected Val = getLocalOrGlobal(LocalName)) - return *Val; - else - llvm::consumeError(Val.takeError()); - return Default.str(); - } + std::string getLocalOrGlobal(StringRef LocalName, StringRef Default) const; /// Read a named option from the ``Context`` and parse it as an /// integral type ``T``. /// /// Reads the option with the check-local name \p LocalName from the - /// ``CheckOptions``. If the corresponding key is not present, returns - /// a ``MissingOptionError``. If the corresponding key can't be parsed as - /// a ``T``, return an ``UnparseableIntegerOptionError``. + /// ``CheckOptions``. If the corresponding key is not present, return + /// ``None``. + /// + /// If the corresponding key can't be parsed as a ``T``, emit a + /// diagnostic and return ``None``. template - std::enable_if_t::value, llvm::Expected> + std::enable_if_t::value, llvm::Optional> get(StringRef LocalName) const { - if (llvm::Expected Value = get(LocalName)) { + if (llvm::Optional Value = get(LocalName)) { T Result{}; if (!StringRef(*Value).getAsInteger(10, Result)) return Result; - return llvm::make_error( - (NamePrefix + LocalName).str(), *Value); - } else - return std::move(Value.takeError()); + diagnoseBadIntegerOption(NamePrefix + LocalName, *Value); + } + return None; } /// Read a named option from the ``Context`` and parse it as an /// integral type ``T``. /// /// Reads the option with the check-local name \p LocalName from the - /// ``CheckOptions``. If the corresponding key is not present or it can't be - /// parsed as a ``T``, returns \p Default. + /// ``CheckOptions``. If the corresponding key is not present, return + /// \p Default. + /// + /// If the corresponding key can't be parsed as a ``T``, emit a + /// diagnostic and return \p Default. template std::enable_if_t::value, T> get(StringRef LocalName, T Default) const { - if (llvm::Expected ValueOr = get(LocalName)) - return *ValueOr; - else - reportOptionParsingError(ValueOr.takeError()); - return Default; + return get(LocalName).getValueOr(Default); } /// Read a named option from the ``Context`` and parse it as an @@ -288,27 +223,27 @@ public: /// Reads the option with the check-local name \p LocalName from local or /// global ``CheckOptions``. Gets local option first. If local is not /// present, falls back to get global option. If global option is not - /// present either, returns a ``MissingOptionError``. If the corresponding - /// key can't be parsed as a ``T``, return an - /// ``UnparseableIntegerOptionError``. + /// present either, return ``None``. + /// + /// If the corresponding key can't be parsed as a ``T``, emit a + /// diagnostic and return ``None``. template - std::enable_if_t::value, llvm::Expected> + std::enable_if_t::value, llvm::Optional> getLocalOrGlobal(StringRef LocalName) const { - llvm::Expected ValueOr = get(LocalName); + llvm::Optional ValueOr = get(LocalName); bool IsGlobal = false; if (!ValueOr) { IsGlobal = true; - llvm::consumeError(ValueOr.takeError()); ValueOr = getLocalOrGlobal(LocalName); if (!ValueOr) - return std::move(ValueOr.takeError()); + return None; } T Result{}; if (!StringRef(*ValueOr).getAsInteger(10, Result)) return Result; - return llvm::make_error( - (IsGlobal ? LocalName.str() : (NamePrefix + LocalName).str()), - *ValueOr); + diagnoseBadIntegerOption( + IsGlobal ? Twine(LocalName) : NamePrefix + LocalName, *ValueOr); + return None; } /// Read a named option from the ``Context`` and parse it as an @@ -317,54 +252,53 @@ public: /// Reads the option with the check-local name \p LocalName from local or /// global ``CheckOptions``. Gets local option first. If local is not /// present, falls back to get global option. If global option is not - /// present either or it can't be parsed as a ``T``, returns \p Default. + /// present either, return \p Default. + /// + /// If the corresponding key can't be parsed as a ``T``, emit a + /// diagnostic and return \p Default. template std::enable_if_t::value, T> getLocalOrGlobal(StringRef LocalName, T Default) const { - if (llvm::Expected ValueOr = getLocalOrGlobal(LocalName)) - return *ValueOr; - else - reportOptionParsingError(ValueOr.takeError()); - return Default; + return getLocalOrGlobal(LocalName).getValueOr(Default); } /// Read a named option from the ``Context`` and parse it as an /// enum type ``T``. /// /// Reads the option with the check-local name \p LocalName from the - /// ``CheckOptions``. If the corresponding key is not present, returns a - /// ``MissingOptionError``. If the key can't be parsed as a ``T`` returns a - /// ``UnparseableEnumOptionError``. + /// ``CheckOptions``. If the corresponding key is not present, return + /// ``None``. + /// + /// If the corresponding key can't be parsed as a ``T``, emit a + /// diagnostic and return ``None``. /// /// \ref clang::tidy::OptionEnumMapping must be specialized for ``T`` to /// supply the mapping required to convert between ``T`` and a string. template - std::enable_if_t::value, llvm::Expected> + std::enable_if_t::value, llvm::Optional> get(StringRef LocalName, bool IgnoreCase = false) const { - if (llvm::Expected ValueOr = + if (llvm::Optional ValueOr = getEnumInt(LocalName, typeEraseMapping(), false, IgnoreCase)) return static_cast(*ValueOr); - else - return std::move(ValueOr.takeError()); + return None; } /// Read a named option from the ``Context`` and parse it as an /// enum type ``T``. /// /// Reads the option with the check-local name \p LocalName from the - /// ``CheckOptions``. If the corresponding key is not present or it can't be - /// parsed as a ``T``, returns \p Default. + /// ``CheckOptions``. If the corresponding key is not present, return + /// \p Default. + /// + /// If the corresponding key can't be parsed as a ``T``, emit a + /// diagnostic and return \p Default. /// /// \ref clang::tidy::OptionEnumMapping must be specialized for ``T`` to /// supply the mapping required to convert between ``T`` and a string. template std::enable_if_t::value, T> get(StringRef LocalName, T Default, bool IgnoreCase = false) const { - if (auto ValueOr = get(LocalName, IgnoreCase)) - return *ValueOr; - else - reportOptionParsingError(ValueOr.takeError()); - return Default; + return get(LocalName, IgnoreCase).getValueOr(Default); } /// Read a named option from the ``Context`` and parse it as an @@ -373,19 +307,20 @@ public: /// Reads the option with the check-local name \p LocalName from local or /// global ``CheckOptions``. Gets local option first. If local is not /// present, falls back to get global option. If global option is not - /// present either, returns a ``MissingOptionError``. If the key can't be - /// parsed as a ``T`` returns a ``UnparseableEnumOptionError``. + /// present either, returns ``None``. + /// + /// If the corresponding key can't be parsed as a ``T``, emit a + /// diagnostic and return ``None``. /// /// \ref clang::tidy::OptionEnumMapping must be specialized for ``T`` to /// supply the mapping required to convert between ``T`` and a string. template - std::enable_if_t::value, llvm::Expected> + std::enable_if_t::value, llvm::Optional> getLocalOrGlobal(StringRef LocalName, bool IgnoreCase = false) const { - if (llvm::Expected ValueOr = + if (llvm::Optional ValueOr = getEnumInt(LocalName, typeEraseMapping(), true, IgnoreCase)) return static_cast(*ValueOr); - else - return std::move(ValueOr.takeError()); + return None; } /// Read a named option from the ``Context`` and parse it as an @@ -394,7 +329,10 @@ public: /// Reads the option with the check-local name \p LocalName from local or /// global ``CheckOptions``. Gets local option first. If local is not /// present, falls back to get global option. If global option is not - /// present either or it can't be parsed as a ``T``, returns \p Default. + /// present either return \p Default. + /// + /// If the corresponding key can't be parsed as a ``T``, emit a + /// diagnostic and return \p Default. /// /// \ref clang::tidy::OptionEnumMapping must be specialized for ``T`` to /// supply the mapping required to convert between ``T`` and a string. @@ -402,36 +340,7 @@ public: std::enable_if_t::value, T> getLocalOrGlobal(StringRef LocalName, T Default, bool IgnoreCase = false) const { - if (auto ValueOr = getLocalOrGlobal(LocalName, IgnoreCase)) - return *ValueOr; - else - reportOptionParsingError(ValueOr.takeError()); - return Default; - } - - /// Returns the value for the option \p LocalName represented as a ``T``. - /// If the option is missing returns None, if the option can't be parsed - /// as a ``T``, log that to stderr and return None. - template - llvm::Optional getOptional(StringRef LocalName) const { - if (auto ValueOr = get(LocalName)) - return *ValueOr; - else - reportOptionParsingError(ValueOr.takeError()); - return llvm::None; - } - - /// Returns the value for the local or global option \p LocalName - /// represented as a ``T``. - /// If the option is missing returns None, if the - /// option can't be parsed as a ``T``, log that to stderr and return None. - template - llvm::Optional getOptionalLocalOrGlobal(StringRef LocalName) const { - if (auto ValueOr = getLocalOrGlobal(LocalName)) - return *ValueOr; - else - reportOptionParsingError(ValueOr.takeError()); - return llvm::None; + return getLocalOrGlobal(LocalName, IgnoreCase).getValueOr(Default); } /// Stores an option with the check-local name \p LocalName with @@ -470,7 +379,7 @@ public: private: using NameAndValue = std::pair; - llvm::Expected getEnumInt(StringRef LocalName, + llvm::Optional getEnumInt(StringRef LocalName, ArrayRef Mapping, bool CheckGlobal, bool IgnoreCase) const; @@ -491,8 +400,6 @@ public: void storeInt(ClangTidyOptions::OptionMap &Options, StringRef LocalName, int64_t Value) const; - /// Emits a diagnostic if \p Err is not a MissingOptionError. - void reportOptionParsingError(llvm::Error &&Err) const; std::string NamePrefix; const ClangTidyOptions::OptionMap &CheckOptions; @@ -516,44 +423,27 @@ protected: /// Read a named option from the ``Context`` and parse it as a bool. /// /// Reads the option with the check-local name \p LocalName from the -/// ``CheckOptions``. If the corresponding key is not present, returns -/// a ``MissingOptionError``. If the corresponding key can't be parsed as -/// a bool, return an ``UnparseableIntegerOptionError``. +/// ``CheckOptions``. If the corresponding key is not present, return +/// ``None``. +/// +/// If the corresponding key can't be parsed as a bool, emit a +/// diagnostic and return ``None``. template <> -llvm::Expected +llvm::Optional ClangTidyCheck::OptionsView::get(StringRef LocalName) const; /// Read a named option from the ``Context`` and parse it as a bool. /// /// Reads the option with the check-local name \p LocalName from the -/// ``CheckOptions``. If the corresponding key is not present or it can't be -/// parsed as a bool, returns \p Default. -template <> -bool ClangTidyCheck::OptionsView::get(StringRef LocalName, - bool Default) const; - -/// Read a named option from the ``Context`` and parse it as a bool. +/// ``CheckOptions``. If the corresponding key is not present, return +/// \p Default. /// -/// Reads the option with the check-local name \p LocalName from local or -/// global ``CheckOptions``. Gets local option first. If local is not -/// present, falls back to get global option. If global option is not -/// present either, returns a ``MissingOptionError``. If the corresponding -/// key can't be parsed as a bool, return an -/// ``UnparseableIntegerOptionError``. +/// If the corresponding key can't be parsed as a bool, emit a +/// diagnostic and return \p Default. template <> -llvm::Expected +llvm::Optional ClangTidyCheck::OptionsView::getLocalOrGlobal(StringRef LocalName) const; -/// Read a named option from the ``Context`` and parse it as a bool. -/// -/// Reads the option with the check-local name \p LocalName from local or -/// global ``CheckOptions``. Gets local option first. If local is not -/// present, falls back to get global option. If global option is not -/// present either or it can't be parsed as a bool, returns \p Default. -template <> -bool ClangTidyCheck::OptionsView::getLocalOrGlobal(StringRef LocalName, - bool Default) const; - /// Stores an option with the check-local name \p LocalName with /// bool value \p Value to \p Options. template <> @@ -561,18 +451,6 @@ void ClangTidyCheck::OptionsView::store( ClangTidyOptions::OptionMap &Options, StringRef LocalName, bool Value) const; -/// Returns the value for the option \p LocalName. -/// If the option is missing returns None. -template <> -Optional ClangTidyCheck::OptionsView::getOptional( - StringRef LocalName) const; - -/// Returns the value for the local or global option \p LocalName. -/// If the option is missing returns None. -template <> -Optional -ClangTidyCheck::OptionsView::getOptionalLocalOrGlobal( - StringRef LocalName) const; } // namespace tidy } // namespace clang diff --git a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp index 48c5b0b2f4b7918af67ffb37fbbbc79226d85879..9e45f0aea798f769a88368cf53687cda18aeb11f 100644 --- a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp +++ b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp @@ -260,11 +260,11 @@ std::string ClangTidyContext::getCheckName(unsigned DiagnosticID) const { ClangTidyDiagnosticConsumer::ClangTidyDiagnosticConsumer( ClangTidyContext &Ctx, DiagnosticsEngine *ExternalDiagEngine, - bool RemoveIncompatibleErrors) + bool RemoveIncompatibleErrors, bool GetFixesFromNotes) : Context(Ctx), ExternalDiagEngine(ExternalDiagEngine), RemoveIncompatibleErrors(RemoveIncompatibleErrors), - LastErrorRelatesToUserCode(false), LastErrorPassesLineFilter(false), - LastErrorWasIgnored(false) {} + GetFixesFromNotes(GetFixesFromNotes), LastErrorRelatesToUserCode(false), + LastErrorPassesLineFilter(false), LastErrorWasIgnored(false) {} void ClangTidyDiagnosticConsumer::finalizeLastError() { if (!Errors.empty()) { @@ -374,6 +374,24 @@ bool shouldSuppressDiagnostic(DiagnosticsEngine::Level DiagLevel, Context, AllowIO); } +const llvm::StringMap * +getFixIt(const tooling::Diagnostic &Diagnostic, bool GetFixFromNotes) { + if (!Diagnostic.Message.Fix.empty()) + return &Diagnostic.Message.Fix; + if (!GetFixFromNotes) + return nullptr; + const llvm::StringMap *Result = nullptr; + for (const auto &Note : Diagnostic.Notes) { + if (!Note.Fix.empty()) { + if (Result) + // We have 2 different fixes in notes, bail out. + return nullptr; + Result = &Note.Fix; + } + } + return Result; +} + } // namespace tidy } // namespace clang @@ -658,7 +676,7 @@ void ClangTidyDiagnosticConsumer::removeIncompatibleErrors() { std::pair *>> ErrorFixes; for (auto &Error : Errors) { - if (const auto *Fix = tooling::selectFirstFix(Error)) + if (const auto *Fix = getFixIt(Error, GetFixesFromNotes)) ErrorFixes.emplace_back( &Error, const_cast *>(Fix)); } diff --git a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h index 079293149e90ffdfde186f5af42ed48e0db1c429..13372cc626b9e3d1ba93c8ac30b114f13183636a 100644 --- a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h +++ b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h @@ -226,6 +226,13 @@ bool shouldSuppressDiagnostic(DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info, ClangTidyContext &Context, bool AllowIO = true); +/// Gets the Fix attached to \p Diagnostic. +/// If there isn't a Fix attached to the diagnostic and \p AnyFix is true, Check +/// to see if exactly one note has a Fix and return it. Otherwise return +/// nullptr. +const llvm::StringMap * +getFixIt(const tooling::Diagnostic &Diagnostic, bool AnyFix); + /// A diagnostic consumer that turns each \c Diagnostic into a /// \c SourceManager-independent \c ClangTidyError. // @@ -235,7 +242,8 @@ class ClangTidyDiagnosticConsumer : public DiagnosticConsumer { public: ClangTidyDiagnosticConsumer(ClangTidyContext &Ctx, DiagnosticsEngine *ExternalDiagEngine = nullptr, - bool RemoveIncompatibleErrors = true); + bool RemoveIncompatibleErrors = true, + bool GetFixesFromNotes = false); // FIXME: The concept of converting between FixItHints and Replacements is // more generic and should be pulled out into a more useful Diagnostics @@ -265,6 +273,7 @@ private: ClangTidyContext &Context; DiagnosticsEngine *ExternalDiagEngine; bool RemoveIncompatibleErrors; + bool GetFixesFromNotes; std::vector Errors; std::unique_ptr HeaderFilter; bool LastErrorRelatesToUserCode; diff --git a/clang-tools-extra/clang-tidy/ExpandModularHeadersPPCallbacks.cpp b/clang-tools-extra/clang-tidy/ExpandModularHeadersPPCallbacks.cpp index 12795f0468fdb38bf776c401fe9d09eb0fd44711..23765da1b46f5aad4fa162322b3ff2baa311c896 100644 --- a/clang-tools-extra/clang-tidy/ExpandModularHeadersPPCallbacks.cpp +++ b/clang-tools-extra/clang-tidy/ExpandModularHeadersPPCallbacks.cpp @@ -230,7 +230,7 @@ void ExpandModularHeadersPPCallbacks::HasInclude(SourceLocation Loc, StringRef, void ExpandModularHeadersPPCallbacks::PragmaOpenCLExtension( SourceLocation NameLoc, const IdentifierInfo *, SourceLocation StateLoc, unsigned) { - // FIME: Figure out whether it's the right location to parse to. + // FIXME: Figure out whether it's the right location to parse to. parseToLocation(NameLoc); } void ExpandModularHeadersPPCallbacks::PragmaWarning(SourceLocation Loc, @@ -256,7 +256,7 @@ void ExpandModularHeadersPPCallbacks::MacroExpands(const Token &MacroNameTok, const MacroDefinition &, SourceRange Range, const MacroArgs *) { - // FIME: Figure out whether it's the right location to parse to. + // FIXME: Figure out whether it's the right location to parse to. parseToLocation(Range.getBegin()); } void ExpandModularHeadersPPCallbacks::MacroDefined(const Token &MacroNameTok, @@ -271,12 +271,12 @@ void ExpandModularHeadersPPCallbacks::MacroUndefined( void ExpandModularHeadersPPCallbacks::Defined(const Token &MacroNameTok, const MacroDefinition &, SourceRange Range) { - // FIME: Figure out whether it's the right location to parse to. + // FIXME: Figure out whether it's the right location to parse to. parseToLocation(Range.getBegin()); } void ExpandModularHeadersPPCallbacks::SourceRangeSkipped( SourceRange Range, SourceLocation EndifLoc) { - // FIME: Figure out whether it's the right location to parse to. + // FIXME: Figure out whether it's the right location to parse to. parseToLocation(EndifLoc); } void ExpandModularHeadersPPCallbacks::If(SourceLocation Loc, SourceRange, diff --git a/clang-tools-extra/clang-tidy/abseil/DurationFactoryFloatCheck.cpp b/clang-tools-extra/clang-tidy/abseil/DurationFactoryFloatCheck.cpp index 1f1dc07351ba43df8dced889cb7ee7eb6a781eb2..4b0ef1b3f4ee094208d7593e8becbaef622fbe8b 100644 --- a/clang-tools-extra/clang-tidy/abseil/DurationFactoryFloatCheck.cpp +++ b/clang-tools-extra/clang-tidy/abseil/DurationFactoryFloatCheck.cpp @@ -59,10 +59,8 @@ void DurationFactoryFloatCheck::check(const MatchFinder::MatchResult &Result) { SimpleArg = stripFloatLiteralFraction(Result, *Arg); if (SimpleArg) { - diag(MatchedCall->getBeginLoc(), - (llvm::Twine("use the integer version of absl::") + - MatchedCall->getDirectCallee()->getName()) - .str()) + diag(MatchedCall->getBeginLoc(), "use the integer version of absl::%0") + << MatchedCall->getDirectCallee()->getName() << FixItHint::CreateReplacement(Arg->getSourceRange(), *SimpleArg); } } diff --git a/clang-tools-extra/clang-tidy/abseil/StringFindStartswithCheck.cpp b/clang-tools-extra/clang-tidy/abseil/StringFindStartswithCheck.cpp index 850631300f7ab0a9a3a10ba18f5feeaf1f762d48..aa43e7dc38851232881d9e7833e33ec9c318cac6 100644 --- a/clang-tools-extra/clang-tidy/abseil/StringFindStartswithCheck.cpp +++ b/clang-tools-extra/clang-tidy/abseil/StringFindStartswithCheck.cpp @@ -83,24 +83,18 @@ void StringFindStartswithCheck::check(const MatchFinder::MatchResult &Result) { Context.getLangOpts()); // Create the StartsWith string, negating if comparison was "!=". - bool Neg = ComparisonExpr->getOpcodeStr() == "!="; - StringRef StartswithStr; - if (Neg) { - StartswithStr = "!absl::StartsWith"; - } else { - StartswithStr = "absl::StartsWith"; - } + bool Neg = ComparisonExpr->getOpcode() == BO_NE; // Create the warning message and a FixIt hint replacing the original expr. - auto Diagnostic = - diag(ComparisonExpr->getBeginLoc(), - (StringRef("use ") + StartswithStr + " instead of find() " + - ComparisonExpr->getOpcodeStr() + " 0") - .str()); + auto Diagnostic = diag(ComparisonExpr->getBeginLoc(), + "use %select{absl::StartsWith|!absl::StartsWith}0 " + "instead of find() %select{==|!=}0 0") + << Neg; Diagnostic << FixItHint::CreateReplacement( ComparisonExpr->getSourceRange(), - (StartswithStr + "(" + HaystackExprCode + ", " + NeedleExprCode + ")") + ((Neg ? "!absl::StartsWith(" : "absl::StartsWith(") + HaystackExprCode + + ", " + NeedleExprCode + ")") .str()); // Create a preprocessor #include FixIt hint (CreateIncludeInsertion checks diff --git a/clang-tools-extra/clang-tidy/altera/StructPackAlignCheck.cpp b/clang-tools-extra/clang-tidy/altera/StructPackAlignCheck.cpp index 9f28a22a9d03ec93092c093c3e6b259da709abd6..a2178befa9df6dbd5fef98251b4ef3dbd11b2549 100644 --- a/clang-tools-extra/clang-tidy/altera/StructPackAlignCheck.cpp +++ b/clang-tools-extra/clang-tidy/altera/StructPackAlignCheck.cpp @@ -11,7 +11,6 @@ #include "clang/AST/RecordLayout.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include -#include using namespace clang::ast_matchers; @@ -109,15 +108,13 @@ void StructPackAlignCheck::check(const MatchFinder::MatchResult &Result) { AlignedAttr *Attribute = Struct->getAttr(); std::string NewAlignQuantity = std::to_string((int)NewAlign.getQuantity()); if (Attribute) { - std::ostringstream FixItString; - FixItString << "aligned(" << NewAlignQuantity << ")"; - FixIt = - FixItHint::CreateReplacement(Attribute->getRange(), FixItString.str()); + FixIt = FixItHint::CreateReplacement( + Attribute->getRange(), + (Twine("aligned(") + NewAlignQuantity + ")").str()); } else { - std::ostringstream FixItString; - FixItString << " __attribute__((aligned(" << NewAlignQuantity << ")))"; - FixIt = FixItHint::CreateInsertion(Struct->getEndLoc().getLocWithOffset(1), - FixItString.str()); + FixIt = FixItHint::CreateInsertion( + Struct->getEndLoc().getLocWithOffset(1), + (Twine(" __attribute__((aligned(") + NewAlignQuantity + ")))").str()); } // And suggest the minimum power-of-two alignment for the struct as a whole diff --git a/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp index a78e93aff550789839105bebeebde942514b9a03..85af540b48fbe5e51685879334633ebae96dd609 100644 --- a/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp @@ -72,10 +72,8 @@ void ExceptionEscapeCheck::check(const MatchFinder::MatchResult &Result) { utils::ExceptionAnalyzer::State::Throwing) // FIXME: We should provide more information about the exact location where // the exception is thrown, maybe the full path the exception escapes - diag(MatchedDecl->getLocation(), - "an exception may be thrown in function %0 " - - "which should not throw exceptions") + diag(MatchedDecl->getLocation(), "an exception may be thrown in function " + "%0 which should not throw exceptions") << MatchedDecl; } diff --git a/clang-tools-extra/clang-tidy/bugprone/ParentVirtualCallCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/ParentVirtualCallCheck.cpp index 6b8b7fc7ffc54e009e6ab11e076bd5a8d4648a54..8f21678f0c9f1fc43c9f9d3073b0d5fa400f743b 100644 --- a/clang-tools-extra/clang-tidy/bugprone/ParentVirtualCallCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/ParentVirtualCallCheck.cpp @@ -137,9 +137,9 @@ void ParentVirtualCallCheck::check(const MatchFinder::MatchResult &Result) { assert(Member->getQualifierLoc().getSourceRange().getBegin().isValid()); auto Diag = diag(Member->getQualifierLoc().getSourceRange().getBegin(), "qualified name '%0' refers to a member overridden " - "in subclass%1; did you mean %2?") + "in %plural{1:subclass|:subclasses}1; did you mean %2?") << getExprAsString(*Member, *Result.Context) - << (Parents.size() > 1 ? "es" : "") << ParentsStr; + << static_cast(Parents.size()) << ParentsStr; // Propose a fix if there's only one parent class... if (Parents.size() == 1 && diff --git a/clang-tools-extra/clang-tidy/bugprone/RedundantBranchConditionCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/RedundantBranchConditionCheck.cpp index 2b0d9630527b4526da2711b538cfffb95169208d..3998e2bc1de066d8761854e6b0fe4c11c3ab8b61 100644 --- a/clang-tools-extra/clang-tidy/bugprone/RedundantBranchConditionCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/RedundantBranchConditionCheck.cpp @@ -48,23 +48,23 @@ void RedundantBranchConditionCheck::registerMatchers(MatchFinder *Finder) { .bind(CondVarStr); Finder->addMatcher( ifStmt( - hasCondition(ignoringParenImpCasts(anyOf( + hasCondition(anyOf( declRefExpr(hasDeclaration(ImmutableVar)).bind(OuterIfVar1Str), - binaryOperator(hasOperatorName("&&"), - hasEitherOperand(ignoringParenImpCasts( - declRefExpr(hasDeclaration(ImmutableVar)) - .bind(OuterIfVar2Str))))))), + binaryOperator( + hasOperatorName("&&"), + hasEitherOperand(declRefExpr(hasDeclaration(ImmutableVar)) + .bind(OuterIfVar2Str))))), hasThen(hasDescendant( - ifStmt(hasCondition(ignoringParenImpCasts( - anyOf(declRefExpr(hasDeclaration(varDecl( - equalsBoundNode(CondVarStr)))) - .bind(InnerIfVar1Str), - binaryOperator( - hasAnyOperatorName("&&", "||"), - hasEitherOperand(ignoringParenImpCasts( - declRefExpr(hasDeclaration(varDecl( + ifStmt(hasCondition(anyOf( + declRefExpr(hasDeclaration( + varDecl(equalsBoundNode(CondVarStr)))) + .bind(InnerIfVar1Str), + binaryOperator( + hasAnyOperatorName("&&", "||"), + hasEitherOperand( + declRefExpr(hasDeclaration(varDecl( equalsBoundNode(CondVarStr)))) - .bind(InnerIfVar2Str)))))))) + .bind(InnerIfVar2Str)))))) .bind(InnerIfStr))), forFunction(functionDecl().bind(FuncStr))) .bind(OuterIfStr), diff --git a/clang-tools-extra/clang-tidy/bugprone/RedundantBranchConditionCheck.h b/clang-tools-extra/clang-tidy/bugprone/RedundantBranchConditionCheck.h index 7b38d59a0121d46373afe2e2d86cd11e827ed86c..a086b269e64d16c429bf08402bb613ec30ba6926 100644 --- a/clang-tools-extra/clang-tidy/bugprone/RedundantBranchConditionCheck.h +++ b/clang-tools-extra/clang-tidy/bugprone/RedundantBranchConditionCheck.h @@ -26,6 +26,9 @@ public: : ClangTidyCheck(Name, Context) {} void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + llvm::Optional getCheckTraversalKind() const override { + return TK_IgnoreUnlessSpelledInSource; + } }; } // namespace bugprone diff --git a/clang-tools-extra/clang-tidy/bugprone/SuspiciousEnumUsageCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/SuspiciousEnumUsageCheck.cpp index 3faa28c0158a030792a6b48e9bea6f85bacb1ca0..73068610a53b3edbcfda1f08553232cd58ea6f39 100644 --- a/clang-tools-extra/clang-tidy/bugprone/SuspiciousEnumUsageCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/SuspiciousEnumUsageCheck.cpp @@ -118,30 +118,28 @@ void SuspiciousEnumUsageCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { void SuspiciousEnumUsageCheck::registerMatchers(MatchFinder *Finder) { const auto EnumExpr = [](StringRef RefName, StringRef DeclName) { - return expr(ignoringImpCasts(expr().bind(RefName)), - ignoringImpCasts(hasType(enumDecl().bind(DeclName)))); + return expr(hasType(enumDecl().bind(DeclName))).bind(RefName); }; Finder->addMatcher( - binaryOperator(hasOperatorName("|"), hasLHS(EnumExpr("", "enumDecl")), - hasRHS(expr(EnumExpr("", "otherEnumDecl"), - ignoringImpCasts(hasType(enumDecl( - unless(equalsBoundNode("enumDecl")))))))) + binaryOperator( + hasOperatorName("|"), hasLHS(hasType(enumDecl().bind("enumDecl"))), + hasRHS(hasType(enumDecl(unless(equalsBoundNode("enumDecl"))) + .bind("otherEnumDecl")))) .bind("diffEnumOp"), this); Finder->addMatcher( binaryOperator(hasAnyOperatorName("+", "|"), hasLHS(EnumExpr("lhsExpr", "enumDecl")), - hasRHS(expr(EnumExpr("rhsExpr", ""), - ignoringImpCasts(hasType( - enumDecl(equalsBoundNode("enumDecl"))))))), + hasRHS(expr(hasType(enumDecl(equalsBoundNode("enumDecl")))) + .bind("rhsExpr"))), this); Finder->addMatcher( binaryOperator( hasAnyOperatorName("+", "|"), - hasOperands(expr(hasType(isInteger()), unless(EnumExpr("", ""))), + hasOperands(expr(hasType(isInteger()), unless(hasType(enumDecl()))), EnumExpr("enumExpr", "enumDecl"))), this); diff --git a/clang-tools-extra/clang-tidy/bugprone/SuspiciousEnumUsageCheck.h b/clang-tools-extra/clang-tidy/bugprone/SuspiciousEnumUsageCheck.h index f05f3e1c54e03116768f58346d856a6d917f33e9..7541810cdb1b853e19ad8f4362c94940944e7926 100644 --- a/clang-tools-extra/clang-tidy/bugprone/SuspiciousEnumUsageCheck.h +++ b/clang-tools-extra/clang-tidy/bugprone/SuspiciousEnumUsageCheck.h @@ -25,6 +25,9 @@ public: void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + llvm::Optional getCheckTraversalKind() const override { + return TK_IgnoreUnlessSpelledInSource; + } private: void checkSuspiciousBitmaskUsage(const Expr*, const EnumDecl*); diff --git a/clang-tools-extra/clang-tidy/bugprone/SuspiciousMemsetUsageCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/SuspiciousMemsetUsageCheck.cpp index 37748d9fa8ccfb66b0b913129c0669cadbf4300d..341ba6ccc09fa2c91613ff13a98685a9a5a57988 100644 --- a/clang-tools-extra/clang-tidy/bugprone/SuspiciousMemsetUsageCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/SuspiciousMemsetUsageCheck.cpp @@ -35,27 +35,24 @@ void SuspiciousMemsetUsageCheck::registerMatchers(MatchFinder *Finder) { callee(MemsetDecl), hasArgument(1, characterLiteral(equals(static_cast('0'))) .bind("char-zero-fill")), - unless( - eachOf(hasArgument(0, anyOf(hasType(pointsTo(isAnyCharacter())), - hasType(arrayType(hasElementType( - isAnyCharacter()))))), - isInTemplateInstantiation()))), + unless(hasArgument( + 0, anyOf(hasType(pointsTo(isAnyCharacter())), + hasType(arrayType(hasElementType(isAnyCharacter()))))))), this); // Look for memset with an integer literal in its fill_char argument. // Will check if it gets truncated. - Finder->addMatcher(callExpr(callee(MemsetDecl), - hasArgument(1, integerLiteral().bind("num-fill")), - unless(isInTemplateInstantiation())), - this); + Finder->addMatcher( + callExpr(callee(MemsetDecl), + hasArgument(1, integerLiteral().bind("num-fill"))), + this); // Look for memset(x, y, 0) as that is most likely an argument swap. Finder->addMatcher( callExpr(callee(MemsetDecl), unless(hasArgument(1, anyOf(characterLiteral(equals( static_cast('0'))), - integerLiteral()))), - unless(isInTemplateInstantiation())) + integerLiteral())))) .bind("call"), this); } diff --git a/clang-tools-extra/clang-tidy/bugprone/SuspiciousMemsetUsageCheck.h b/clang-tools-extra/clang-tidy/bugprone/SuspiciousMemsetUsageCheck.h index 40746413485fab911eb34ea5ae542ed1a3cec544..0a46f0620ef8155311117157e05bba4efcb09965 100644 --- a/clang-tools-extra/clang-tidy/bugprone/SuspiciousMemsetUsageCheck.h +++ b/clang-tools-extra/clang-tidy/bugprone/SuspiciousMemsetUsageCheck.h @@ -25,6 +25,9 @@ public: : ClangTidyCheck(Name, Context) {} void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + llvm::Optional getCheckTraversalKind() const override { + return TK_IgnoreUnlessSpelledInSource; + } }; } // namespace bugprone diff --git a/clang-tools-extra/clang-tidy/bugprone/UnusedRaiiCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/UnusedRaiiCheck.cpp index 3a52180cf04d1ed19dd139a653e33dd1577b27ec..b87bb9e8ca953496637903060778d9701e9c7234 100644 --- a/clang-tools-extra/clang-tidy/bugprone/UnusedRaiiCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/UnusedRaiiCheck.cpp @@ -25,25 +25,37 @@ AST_MATCHER(CXXRecordDecl, hasNonTrivialDestructor) { void UnusedRaiiCheck::registerMatchers(MatchFinder *Finder) { // Look for temporaries that are constructed in-place and immediately - // destroyed. Look for temporaries created by a functional cast but not for - // those returned from a call. - auto BindTemp = cxxBindTemporaryExpr( - unless(has(ignoringParenImpCasts(callExpr()))), - unless(has(ignoringParenImpCasts(objcMessageExpr())))) - .bind("temp"); + // destroyed. Finder->addMatcher( - traverse(TK_AsIs, - exprWithCleanups( - unless(isInTemplateInstantiation()), - hasParent(compoundStmt().bind("compound")), - hasType(cxxRecordDecl(hasNonTrivialDestructor())), - anyOf(has(ignoringParenImpCasts(BindTemp)), - has(ignoringParenImpCasts(cxxFunctionalCastExpr( - has(ignoringParenImpCasts(BindTemp))))))) - .bind("expr")), + mapAnyOf(cxxConstructExpr, cxxUnresolvedConstructExpr) + .with(hasParent(compoundStmt().bind("compound")), + anyOf(hasType(cxxRecordDecl(hasNonTrivialDestructor())), + hasType(templateSpecializationType( + hasDeclaration(classTemplateDecl(has( + cxxRecordDecl(hasNonTrivialDestructor())))))))) + .bind("expr"), this); } +template +void reportDiagnostic(DiagnosticBuilder D, const T *Node, SourceRange SR, + bool DefaultConstruction) { + const char *Replacement = " give_me_a_name"; + + // If this is a default ctor we have to remove the parens or we'll introduce a + // most vexing parse. + if (DefaultConstruction) { + D << FixItHint::CreateReplacement(CharSourceRange::getTokenRange(SR), + Replacement); + return; + } + + // Otherwise just suggest adding a name. To find the place to insert the name + // find the first TypeLoc in the children of E, which always points to the + // written type. + D << FixItHint::CreateInsertion(SR.getBegin(), Replacement); +} + void UnusedRaiiCheck::check(const MatchFinder::MatchResult &Result) { const auto *E = Result.Nodes.getNodeAs("expr"); @@ -55,35 +67,32 @@ void UnusedRaiiCheck::check(const MatchFinder::MatchResult &Result) { // Don't emit a warning for the last statement in the surrounding compound // statement. const auto *CS = Result.Nodes.getNodeAs("compound"); - if (E == CS->body_back()) + const auto *LastExpr = dyn_cast(CS->body_back()); + + if (LastExpr && E == LastExpr->IgnoreUnlessSpelledInSource()) return; // Emit a warning. auto D = diag(E->getBeginLoc(), "object destroyed immediately after " "creation; did you mean to name the object?"); - const char *Replacement = " give_me_a_name"; - // If this is a default ctor we have to remove the parens or we'll introduce a - // most vexing parse. - const auto *BTE = Result.Nodes.getNodeAs("temp"); - if (const auto *TOE = dyn_cast(BTE->getSubExpr())) - if (TOE->getNumArgs() == 0) { - D << FixItHint::CreateReplacement( - CharSourceRange::getTokenRange(TOE->getParenOrBraceRange()), - Replacement); - return; + if (const auto *Node = dyn_cast(E)) + reportDiagnostic(D, Node, Node->getParenOrBraceRange(), + Node->getNumArgs() == 0 || + isa(Node->getArg(0))); + if (const auto *Node = dyn_cast(E)) { + auto SR = SourceRange(Node->getLParenLoc(), Node->getRParenLoc()); + auto DefaultConstruction = Node->getNumArgs() == 0; + if (!DefaultConstruction) { + auto FirstArg = Node->getArg(0); + DefaultConstruction = isa(FirstArg); + if (auto ILE = dyn_cast(FirstArg)) { + DefaultConstruction = ILE->getNumInits() == 0; + SR = SourceRange(ILE->getLBraceLoc(), ILE->getRBraceLoc()); + } } - - // Otherwise just suggest adding a name. To find the place to insert the name - // find the first TypeLoc in the children of E, which always points to the - // written type. - auto Matches = - match(expr(hasDescendant(typeLoc().bind("t"))), *E, *Result.Context); - if (const auto *TL = selectFirst("t", Matches)) - D << FixItHint::CreateInsertion( - Lexer::getLocForEndOfToken(TL->getEndLoc(), 0, *Result.SourceManager, - getLangOpts()), - Replacement); + reportDiagnostic(D, Node, SR, DefaultConstruction); + } } } // namespace bugprone diff --git a/clang-tools-extra/clang-tidy/bugprone/UnusedRaiiCheck.h b/clang-tools-extra/clang-tidy/bugprone/UnusedRaiiCheck.h index 3608365866997d456292ad792cc38725c2861998..f4a0de02d39a744d743d996977c1b0df8c9b9955 100644 --- a/clang-tools-extra/clang-tidy/bugprone/UnusedRaiiCheck.h +++ b/clang-tools-extra/clang-tidy/bugprone/UnusedRaiiCheck.h @@ -28,6 +28,9 @@ public: } void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + llvm::Optional getCheckTraversalKind() const override { + return TK_IgnoreUnlessSpelledInSource; + } }; } // namespace bugprone diff --git a/clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp index 028fefa9b99e4bb8352377d81402f6aa064ad175..136d8f862b9565013e3dfe7aeb1331d2523aecaf 100644 --- a/clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp @@ -398,7 +398,13 @@ void UseAfterMoveCheck::registerMatchers(MatchFinder *Finder) { hasArgument(0, declRefExpr().bind("arg")), anyOf(hasAncestor(lambdaExpr().bind("containing-lambda")), hasAncestor(functionDecl().bind("containing-func"))), - unless(inDecltypeOrTemplateArg())) + unless(inDecltypeOrTemplateArg()), + // try_emplace is a common maybe-moving function that returns a + // bool to tell callers whether it moved. Ignore std::move inside + // try_emplace to avoid false positives as we don't track uses of + // the bool. + unless(hasParent(cxxMemberCallExpr( + callee(cxxMethodDecl(hasName("try_emplace"))))))) .bind("call-move"); Finder->addMatcher( diff --git a/clang-tools-extra/clang-tidy/bugprone/VirtualNearMissCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/VirtualNearMissCheck.cpp index dc810c69487451256d0a088bd35b2d4c2381b662..150b517811b66c51ac72143b6f4027c791b4f908 100644 --- a/clang-tools-extra/clang-tidy/bugprone/VirtualNearMissCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/VirtualNearMissCheck.cpp @@ -216,9 +216,10 @@ bool VirtualNearMissCheck::isOverriddenByDerivedClass( void VirtualNearMissCheck::registerMatchers(MatchFinder *Finder) { Finder->addMatcher( - cxxMethodDecl(unless(anyOf(isOverride(), cxxConstructorDecl(), - cxxDestructorDecl(), cxxConversionDecl(), - isStatic(), isOverloadedOperator()))) + cxxMethodDecl( + unless(anyOf(isOverride(), isImplicit(), cxxConstructorDecl(), + cxxDestructorDecl(), cxxConversionDecl(), isStatic(), + isOverloadedOperator()))) .bind("method"), this); } @@ -233,15 +234,7 @@ void VirtualNearMissCheck::check(const MatchFinder::MatchResult &Result) { assert(DerivedRD); for (const auto &BaseSpec : DerivedRD->bases()) { - const auto *BaseRD = BaseSpec.getType()->getAsCXXRecordDecl(); - if (const auto *TST = - dyn_cast(BaseSpec.getType())) { - auto TN = TST->getTemplateName(); - BaseRD = - dyn_cast(TN.getAsTemplateDecl()->getTemplatedDecl()); - } - - if (BaseRD) { + if (const auto *BaseRD = BaseSpec.getType()->getAsCXXRecordDecl()) { for (const auto *BaseMD : BaseRD->methods()) { if (!isPossibleToBeOverridden(BaseMD)) continue; @@ -257,12 +250,16 @@ void VirtualNearMissCheck::check(const MatchFinder::MatchResult &Result) { auto Range = CharSourceRange::getTokenRange( SourceRange(DerivedMD->getLocation())); - diag(DerivedMD->getBeginLoc(), - "method '%0' has a similar name and the same signature as " - "virtual method '%1'; did you mean to override it?") + bool ApplyFix = !BaseMD->isTemplateInstantiation() && + !DerivedMD->isTemplateInstantiation(); + auto Diag = + diag(DerivedMD->getBeginLoc(), + "method '%0' has a similar name and the same signature as " + "virtual method '%1'; did you mean to override it?") << DerivedMD->getQualifiedNameAsString() - << BaseMD->getQualifiedNameAsString() - << FixItHint::CreateReplacement(Range, BaseMD->getName()); + << BaseMD->getQualifiedNameAsString(); + if (ApplyFix) + Diag << FixItHint::CreateReplacement(Range, BaseMD->getName()); } } } diff --git a/clang-tools-extra/clang-tidy/bugprone/VirtualNearMissCheck.h b/clang-tools-extra/clang-tidy/bugprone/VirtualNearMissCheck.h index ec902515f7062fa2e8122f773b8cd7d2ff7494f8..69ae278f2e2c97ba48dc1afc5662754f38229f29 100644 --- a/clang-tools-extra/clang-tidy/bugprone/VirtualNearMissCheck.h +++ b/clang-tools-extra/clang-tidy/bugprone/VirtualNearMissCheck.h @@ -32,9 +32,6 @@ public: } void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; - llvm::Optional getCheckTraversalKind() const override { - return TK_IgnoreUnlessSpelledInSource; - } private: /// Check if the given method is possible to be overridden by some other diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/MacroUsageCheck.cpp b/clang-tools-extra/clang-tidy/cppcoreguidelines/MacroUsageCheck.cpp index 4f56df0b4ff773fdd3edd123bba14cb857e5d109..a6f6ca4c1abd646f47ad61b8caba30750ca7d689 100644 --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/MacroUsageCheck.cpp +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/MacroUsageCheck.cpp @@ -47,6 +47,8 @@ public: return; StringRef MacroName = MacroNameTok.getIdentifierInfo()->getName(); + if (MacroName == "__GCC_HAVE_DWARF2_CFI_ASM") + return; if (!CheckCapsOnly && !RegExp.match(MacroName)) Check->warnMacro(MD, MacroName); diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/ProTypeMemberInitCheck.cpp b/clang-tools-extra/clang-tidy/cppcoreguidelines/ProTypeMemberInitCheck.cpp index 6b40e525f7dd50f01e15ff1861f4c9d901fb293a..f4468367ffa32590b1d80483bb0bd3a9c57ab8e8 100644 --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/ProTypeMemberInitCheck.cpp +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/ProTypeMemberInitCheck.cpp @@ -439,10 +439,9 @@ void ProTypeMemberInitCheck::checkMissingMemberInitializer( DiagnosticBuilder Diag = diag(Ctor ? Ctor->getBeginLoc() : ClassDecl.getLocation(), - IsUnion - ? "union constructor should initialize one of these fields: %0" - : "constructor does not initialize these fields: %0") - << toCommaSeparatedString(OrderedFields, AllFieldsToInit); + "%select{|union }0constructor %select{does not|should}0 initialize " + "%select{|one of }0these fields: %1") + << IsUnion << toCommaSeparatedString(OrderedFields, AllFieldsToInit); // Do not propose fixes for constructors in macros since we cannot place them // correctly. diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/SpecialMemberFunctionsCheck.cpp b/clang-tools-extra/clang-tidy/cppcoreguidelines/SpecialMemberFunctionsCheck.cpp index c4684d41f78f06a6935b4f86b8603b96120cc624..823075f6ff18a6af54e318c888fe85a247dfdaf8 100644 --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/SpecialMemberFunctionsCheck.cpp +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/SpecialMemberFunctionsCheck.cpp @@ -40,18 +40,13 @@ void SpecialMemberFunctionsCheck::storeOptions( void SpecialMemberFunctionsCheck::registerMatchers(MatchFinder *Finder) { Finder->addMatcher( cxxRecordDecl( - eachOf( - has(cxxDestructorDecl(unless(isImplicit())).bind("dtor")), - has(cxxConstructorDecl(isCopyConstructor(), unless(isImplicit())) - .bind("copy-ctor")), - has(cxxMethodDecl(isCopyAssignmentOperator(), - unless(isImplicit())) - .bind("copy-assign")), - has(cxxConstructorDecl(isMoveConstructor(), unless(isImplicit())) - .bind("move-ctor")), - has(cxxMethodDecl(isMoveAssignmentOperator(), - unless(isImplicit())) - .bind("move-assign")))) + eachOf(has(cxxDestructorDecl().bind("dtor")), + has(cxxConstructorDecl(isCopyConstructor()).bind("copy-ctor")), + has(cxxMethodDecl(isCopyAssignmentOperator()) + .bind("copy-assign")), + has(cxxConstructorDecl(isMoveConstructor()).bind("move-ctor")), + has(cxxMethodDecl(isMoveAssignmentOperator()) + .bind("move-assign")))) .bind("class-def"), this); } diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/SpecialMemberFunctionsCheck.h b/clang-tools-extra/clang-tidy/cppcoreguidelines/SpecialMemberFunctionsCheck.h index f232a0a09fbb7e385cd6ffa1195d727f42ad5397..ada765df3c4c4bedaa4536ba711d443db9729f77 100644 --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/SpecialMemberFunctionsCheck.h +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/SpecialMemberFunctionsCheck.h @@ -32,7 +32,9 @@ public: void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; void onEndOfTranslationUnit() override; - + llvm::Optional getCheckTraversalKind() const override { + return TK_IgnoreUnlessSpelledInSource; + } enum class SpecialMemberFunctionKind : uint8_t { Destructor, DefaultDestructor, diff --git a/clang-tools-extra/clang-tidy/llvm/PreferRegisterOverUnsignedCheck.cpp b/clang-tools-extra/clang-tidy/llvm/PreferRegisterOverUnsignedCheck.cpp index 17e75abd724ce5a34ee6863c4c9ed9426ce0e2f4..6e8cabeb4e64ee56e4d108804a82f4e476317848 100644 --- a/clang-tools-extra/clang-tidy/llvm/PreferRegisterOverUnsignedCheck.cpp +++ b/clang-tools-extra/clang-tidy/llvm/PreferRegisterOverUnsignedCheck.cpp @@ -38,27 +38,27 @@ void PreferRegisterOverUnsignedCheck::check( const auto *VarType = Result.Nodes.getNodeAs("varType"); const auto *UserVarDecl = Result.Nodes.getNodeAs("var"); - StringRef Replacement = "llvm::Register"; + bool NeedsQualification = true; const DeclContext *Context = UserVarDecl->getDeclContext(); while (Context) { if (const auto *Namespace = dyn_cast(Context)) if (isa(Namespace->getDeclContext()) && Namespace->getName() == "llvm") - Replacement = "Register"; + NeedsQualification = false; for (const auto *UsingDirective : Context->using_directives()) { const NamespaceDecl *Namespace = UsingDirective->getNominatedNamespace(); if (isa(Namespace->getDeclContext()) && Namespace->getName() == "llvm") - Replacement = "Register"; + NeedsQualification = false; } Context = Context->getParent(); } diag(UserVarDecl->getLocation(), - "variable %0 declared as %1; use '%2' instead") - << UserVarDecl << *VarType << Replacement + "variable %0 declared as %1; use '%select{|llvm::}2Register' instead") + << UserVarDecl << *VarType << NeedsQualification << FixItHint::CreateReplacement( UserVarDecl->getTypeSourceInfo()->getTypeLoc().getSourceRange(), - Replacement); + NeedsQualification ? "llvm::Register" : "Register"); } } // namespace llvm_check diff --git a/clang-tools-extra/clang-tidy/misc/StaticAssertCheck.cpp b/clang-tools-extra/clang-tidy/misc/StaticAssertCheck.cpp index 2d6504cae689a29391e415140fc9a198df8f86b0..e9ea69aaeb323a08aee350f125c5ea7feb9d9c8e 100644 --- a/clang-tools-extra/clang-tidy/misc/StaticAssertCheck.cpp +++ b/clang-tools-extra/clang-tidy/misc/StaticAssertCheck.cpp @@ -107,17 +107,16 @@ void StaticAssertCheck::check(const MatchFinder::MatchResult &Result) { FixItHints.push_back( FixItHint::CreateReplacement(SourceRange(AssertLoc), "static_assert")); - std::string StaticAssertMSG = ", \"\""; if (AssertExprRoot) { FixItHints.push_back(FixItHint::CreateRemoval( SourceRange(AssertExprRoot->getOperatorLoc()))); FixItHints.push_back(FixItHint::CreateRemoval( SourceRange(AssertMSG->getBeginLoc(), AssertMSG->getEndLoc()))); - StaticAssertMSG = (Twine(", \"") + AssertMSG->getString() + "\"").str(); + FixItHints.push_back(FixItHint::CreateInsertion( + LastParenLoc, (Twine(", \"") + AssertMSG->getString() + "\"").str())); + } else if (!Opts.CPlusPlus17) { + FixItHints.push_back(FixItHint::CreateInsertion(LastParenLoc, ", \"\"")); } - - FixItHints.push_back( - FixItHint::CreateInsertion(LastParenLoc, StaticAssertMSG)); } diag(AssertLoc, "found assert() that could be replaced by static_assert()") diff --git a/clang-tools-extra/clang-tidy/misc/UnconventionalAssignOperatorCheck.cpp b/clang-tools-extra/clang-tidy/misc/UnconventionalAssignOperatorCheck.cpp index 5fc973223ea3b1b909c85bb069b87d20158491ab..594c83a30e7effae3d85f450deb26699e5b03672 100644 --- a/clang-tools-extra/clang-tidy/misc/UnconventionalAssignOperatorCheck.cpp +++ b/clang-tools-extra/clang-tidy/misc/UnconventionalAssignOperatorCheck.cpp @@ -73,21 +73,18 @@ void UnconventionalAssignOperatorCheck::check( if (const auto *RetStmt = Result.Nodes.getNodeAs("returnStmt")) { diag(RetStmt->getBeginLoc(), "operator=() should always return '*this'"); } else { - static const char *const Messages[][2] = { - {"ReturnType", "operator=() should return '%0&'"}, - {"ArgumentType", - getLangOpts().CPlusPlus11 - ? "operator=() should take '%0 const&', '%0&&' or '%0'" - : "operator=() should take '%0 const&' or '%0'"}, - {"cv", "operator=() should not be marked '%1'"}}; - const auto *Method = Result.Nodes.getNodeAs("method"); - for (const auto &Message : Messages) { - if (Result.Nodes.getNodeAs(Message[0])) - diag(Method->getBeginLoc(), Message[1]) - << Method->getParent()->getName() - << (Method->isConst() ? "const" : "virtual"); - } + if (Result.Nodes.getNodeAs("ReturnType")) + diag(Method->getBeginLoc(), "operator=() should return '%0&'") + << Method->getParent()->getName(); + if (Result.Nodes.getNodeAs("ArgumentType")) + diag(Method->getBeginLoc(), + "operator=() should take '%0 const&'%select{|, '%0&&'}1 or '%0'") + << Method->getParent()->getName() << getLangOpts().CPlusPlus11; + if (Result.Nodes.getNodeAs("cv")) + diag(Method->getBeginLoc(), + "operator=() should not be marked '%select{const|virtual}0'") + << !Method->isConst(); } } diff --git a/clang-tools-extra/clang-tidy/misc/UniqueptrResetReleaseCheck.cpp b/clang-tools-extra/clang-tidy/misc/UniqueptrResetReleaseCheck.cpp index 98b53e33bdaf286b3d9e69ba3928c969a7a3d9b8..dffb03f76a3d96b0211d1fe34a8430ff76b9f985 100644 --- a/clang-tools-extra/clang-tidy/misc/UniqueptrResetReleaseCheck.cpp +++ b/clang-tools-extra/clang-tidy/misc/UniqueptrResetReleaseCheck.cpp @@ -19,18 +19,21 @@ namespace misc { void UniqueptrResetReleaseCheck::registerMatchers(MatchFinder *Finder) { Finder->addMatcher( cxxMemberCallExpr( - on(expr().bind("left")), callee(memberExpr().bind("reset_member")), - callee( - cxxMethodDecl(hasName("reset"), - ofClass(cxxRecordDecl(hasName("::std::unique_ptr"), - decl().bind("left_class"))))), - has(ignoringParenImpCasts(cxxMemberCallExpr( - on(expr().bind("right")), - callee(memberExpr().bind("release_member")), - callee(cxxMethodDecl( - hasName("release"), - ofClass(cxxRecordDecl(hasName("::std::unique_ptr"), - decl().bind("right_class"))))))))) + callee(memberExpr( + member(cxxMethodDecl( + hasName("reset"), + ofClass(cxxRecordDecl(hasName("::std::unique_ptr"), + decl().bind("left_class")))))) + .bind("reset_member")), + hasArgument( + 0, ignoringParenImpCasts(cxxMemberCallExpr( + on(expr().bind("right")), + callee(memberExpr(member(cxxMethodDecl( + hasName("release"), + ofClass(cxxRecordDecl( + hasName("::std::unique_ptr"), + decl().bind("right_class")))))) + .bind("release_member")))))) .bind("reset_call"), this); } @@ -95,34 +98,31 @@ void UniqueptrResetReleaseCheck::check(const MatchFinder::MatchResult &Result) { const auto *ReleaseMember = Result.Nodes.getNodeAs("release_member"); const auto *Right = Result.Nodes.getNodeAs("right"); - const auto *Left = Result.Nodes.getNodeAs("left"); const auto *ResetCall = Result.Nodes.getNodeAs("reset_call"); - std::string LeftText = std::string(clang::Lexer::getSourceText( - CharSourceRange::getTokenRange(Left->getSourceRange()), - *Result.SourceManager, getLangOpts())); - std::string RightText = std::string(clang::Lexer::getSourceText( - CharSourceRange::getTokenRange(Right->getSourceRange()), - *Result.SourceManager, getLangOpts())); - - if (ResetMember->isArrow()) - LeftText = "*" + LeftText; - if (ReleaseMember->isArrow()) - RightText = "*" + RightText; - std::string DiagText; - // Even if x was rvalue, *x is not rvalue anymore. - if (!Right->isRValue() || ReleaseMember->isArrow()) { - RightText = "std::move(" + RightText + ")"; - DiagText = "prefer ptr1 = std::move(ptr2) over ptr1.reset(ptr2.release())"; - } else { - DiagText = - "prefer ptr = ReturnUnique() over ptr.reset(ReturnUnique().release())"; + StringRef AssignmentText = " = "; + StringRef TrailingText = ""; + if (ReleaseMember->isArrow()) { + AssignmentText = " = std::move(*"; + TrailingText = ")"; + } else if (!Right->isRValue()) { + AssignmentText = " = std::move("; + TrailingText = ")"; } - std::string NewText = LeftText + " = " + RightText; - diag(ResetMember->getExprLoc(), DiagText) << FixItHint::CreateReplacement( - CharSourceRange::getTokenRange(ResetCall->getSourceRange()), NewText); + auto D = diag(ResetMember->getExprLoc(), + "prefer 'unique_ptr<>' assignment over 'release' and 'reset'"); + if (ResetMember->isArrow()) + D << FixItHint::CreateInsertion(ResetMember->getBeginLoc(), "*"); + D << FixItHint::CreateReplacement( + CharSourceRange::getCharRange(ResetMember->getOperatorLoc(), + Right->getBeginLoc()), + AssignmentText) + << FixItHint::CreateReplacement( + CharSourceRange::getTokenRange(ReleaseMember->getOperatorLoc(), + ResetCall->getEndLoc()), + TrailingText); } } // namespace misc diff --git a/clang-tools-extra/clang-tidy/modernize/AvoidCArraysCheck.cpp b/clang-tools-extra/clang-tidy/modernize/AvoidCArraysCheck.cpp index bc586d30b378cbcec2936814916882e06bc6a995..872dd1867594080dce75252e31f39a7ee5c3b0a7 100644 --- a/clang-tools-extra/clang-tidy/modernize/AvoidCArraysCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/AvoidCArraysCheck.cpp @@ -57,13 +57,10 @@ void AvoidCArraysCheck::registerMatchers(MatchFinder *Finder) { void AvoidCArraysCheck::check(const MatchFinder::MatchResult &Result) { const auto *ArrayType = Result.Nodes.getNodeAs("typeloc"); - static constexpr llvm::StringLiteral UseArray = llvm::StringLiteral( - "do not declare C-style arrays, use std::array<> instead"); - static constexpr llvm::StringLiteral UseVector = llvm::StringLiteral( - "do not declare C VLA arrays, use std::vector<> instead"); - diag(ArrayType->getBeginLoc(), - ArrayType->getTypePtr()->isVariableArrayType() ? UseVector : UseArray); + "do not declare %select{C-style|C VLA}0 arrays, use " + "%select{std::array<>|std::vector<>}0 instead") + << ArrayType->getTypePtr()->isVariableArrayType(); } } // namespace modernize diff --git a/clang-tools-extra/clang-tidy/modernize/LoopConvertCheck.cpp b/clang-tools-extra/clang-tidy/modernize/LoopConvertCheck.cpp index ec75be9c5f53837d5aaec492fa6dd5a0051ba4d4..9f6ef08ef8397a8405127eb16edd6cf3f3a3806b 100644 --- a/clang-tools-extra/clang-tidy/modernize/LoopConvertCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/LoopConvertCheck.cpp @@ -61,21 +61,16 @@ static const char LoopNameIterator[] = "forLoopIterator"; static const char LoopNameReverseIterator[] = "forLoopReverseIterator"; static const char LoopNamePseudoArray[] = "forLoopPseudoArray"; static const char ConditionBoundName[] = "conditionBound"; -static const char ConditionVarName[] = "conditionVar"; -static const char IncrementVarName[] = "incrementVar"; static const char InitVarName[] = "initVar"; static const char BeginCallName[] = "beginCall"; static const char EndCallName[] = "endCall"; -static const char ConditionEndVarName[] = "conditionEndVar"; static const char EndVarName[] = "endVar"; static const char DerefByValueResultName[] = "derefByValueResult"; static const char DerefByRefResultName[] = "derefByRefResult"; -// shared matchers -static const TypeMatcher anyType() { return anything(); } static const StatementMatcher integerComparisonMatcher() { return expr(ignoringParenImpCasts( - declRefExpr(to(varDecl(hasType(isInteger())).bind(ConditionVarName))))); + declRefExpr(to(varDecl(equalsBoundNode(InitVarName)))))); } static const DeclarationMatcher initToZeroMatcher() { @@ -85,43 +80,42 @@ static const DeclarationMatcher initToZeroMatcher() { } static const StatementMatcher incrementVarMatcher() { - return declRefExpr(to(varDecl(hasType(isInteger())).bind(IncrementVarName))); + return declRefExpr(to(varDecl(equalsBoundNode(InitVarName)))); +} + +static StatementMatcher +arrayConditionMatcher(internal::Matcher LimitExpr) { + return binaryOperator( + anyOf(allOf(hasOperatorName("<"), hasLHS(integerComparisonMatcher()), + hasRHS(LimitExpr)), + allOf(hasOperatorName(">"), hasLHS(LimitExpr), + hasRHS(integerComparisonMatcher())), + allOf(hasOperatorName("!="), + hasOperands(integerComparisonMatcher(), LimitExpr)))); } /// The matcher for loops over arrays. -/// -/// In this general example, assuming 'j' and 'k' are of integral type: /// \code -/// for (int i = 0; j < 3 + 2; ++k) { ... } +/// for (int i = 0; i < 3 + 2; ++i) { ... } /// \endcode /// The following string identifiers are bound to these parts of the AST: -/// ConditionVarName: 'j' (as a VarDecl) /// ConditionBoundName: '3 + 2' (as an Expr) /// InitVarName: 'i' (as a VarDecl) -/// IncrementVarName: 'k' (as a VarDecl) /// LoopName: The entire for loop (as a ForStmt) /// /// Client code will need to make sure that: -/// - The three index variables identified by the matcher are the same -/// VarDecl. /// - The index variable is only used as an array index. /// - All arrays indexed by the loop are the same. StatementMatcher makeArrayLoopMatcher() { StatementMatcher ArrayBoundMatcher = expr(hasType(isInteger())).bind(ConditionBoundName); - return forStmt( - unless(isInTemplateInstantiation()), - hasLoopInit(declStmt(hasSingleDecl(initToZeroMatcher()))), - hasCondition(anyOf( - binaryOperator(hasOperatorName("<"), - hasLHS(integerComparisonMatcher()), - hasRHS(ArrayBoundMatcher)), - binaryOperator(hasOperatorName(">"), hasLHS(ArrayBoundMatcher), - hasRHS(integerComparisonMatcher())))), - hasIncrement( - unaryOperator(hasOperatorName("++"), - hasUnaryOperand(incrementVarMatcher())))) + return forStmt(unless(isInTemplateInstantiation()), + hasLoopInit(declStmt(hasSingleDecl(initToZeroMatcher()))), + hasCondition(arrayConditionMatcher(ArrayBoundMatcher)), + hasIncrement( + unaryOperator(hasOperatorName("++"), + hasUnaryOperand(incrementVarMatcher())))) .bind(LoopNameArray); } @@ -131,27 +125,22 @@ StatementMatcher makeArrayLoopMatcher() { /// catch loops of the following textual forms (regardless of whether the /// iterator type is actually a pointer type or a class type): /// -/// Assuming f, g, and h are of type containerType::iterator, /// \code /// for (containerType::iterator it = container.begin(), -/// e = createIterator(); f != g; ++h) { ... } +/// e = createIterator(); it != e; ++it) { ... } /// for (containerType::iterator it = container.begin(); -/// f != anotherContainer.end(); ++h) { ... } +/// it != anotherContainer.end(); ++it) { ... } /// \endcode /// The following string identifiers are bound to the parts of the AST: /// InitVarName: 'it' (as a VarDecl) -/// ConditionVarName: 'f' (as a VarDecl) /// LoopName: The entire for loop (as a ForStmt) /// In the first example only: /// EndVarName: 'e' (as a VarDecl) -/// ConditionEndVarName: 'g' (as a VarDecl) /// In the second example only: /// EndCallName: 'container.end()' (as a CXXMemberCallExpr) /// /// Client code will need to make sure that: -/// - The iterator variables 'it', 'f', and 'h' are the same. /// - The two containers on which 'begin' and 'end' are called are the same. -/// - If the end iterator variable 'g' is defined, it is the same as 'f'. StatementMatcher makeIteratorLoopMatcher(bool IsReverse) { auto BeginNameMatcher = IsReverse ? hasAnyName("rbegin", "crbegin") @@ -180,13 +169,13 @@ StatementMatcher makeIteratorLoopMatcher(bool IsReverse) { StatementMatcher IteratorBoundMatcher = expr(anyOf(ignoringParenImpCasts( - declRefExpr(to(varDecl().bind(ConditionEndVarName)))), + declRefExpr(to(varDecl(equalsBoundNode(EndVarName))))), ignoringParenImpCasts(expr(EndCallMatcher).bind(EndCallName)), materializeTemporaryExpr(ignoringParenImpCasts( expr(EndCallMatcher).bind(EndCallName))))); - StatementMatcher IteratorComparisonMatcher = expr( - ignoringParenImpCasts(declRefExpr(to(varDecl().bind(ConditionVarName))))); + StatementMatcher IteratorComparisonMatcher = expr(ignoringParenImpCasts( + declRefExpr(to(varDecl(equalsBoundNode(InitVarName)))))); // This matcher tests that a declaration is a CXXRecordDecl that has an // overloaded operator*(). If the operator*() returns by value instead of by @@ -217,13 +206,12 @@ StatementMatcher makeIteratorLoopMatcher(bool IsReverse) { hasIncrement(anyOf( unaryOperator(hasOperatorName("++"), hasUnaryOperand(declRefExpr( - to(varDecl(hasType(pointsTo(anyType()))) - .bind(IncrementVarName))))), + to(varDecl(equalsBoundNode(InitVarName)))))), cxxOperatorCallExpr( hasOverloadedOperatorName("++"), - hasArgument( - 0, declRefExpr(to(varDecl(TestDerefReturnsByValue) - .bind(IncrementVarName)))))))) + hasArgument(0, declRefExpr(to( + varDecl(equalsBoundNode(InitVarName), + TestDerefReturnsByValue)))))))) .bind(IsReverse ? LoopNameReverseIterator : LoopNameIterator); } @@ -233,27 +221,22 @@ StatementMatcher makeIteratorLoopMatcher(bool IsReverse) { /// loops of the following textual forms (regardless of whether the /// iterator type is actually a pointer type or a class type): /// -/// Assuming f, g, and h are of type containerType::iterator, /// \code -/// for (int i = 0, j = container.size(); f < g; ++h) { ... } -/// for (int i = 0; f < container.size(); ++h) { ... } +/// for (int i = 0, j = container.size(); i < j; ++i) { ... } +/// for (int i = 0; i < container.size(); ++i) { ... } /// \endcode /// The following string identifiers are bound to the parts of the AST: /// InitVarName: 'i' (as a VarDecl) -/// ConditionVarName: 'f' (as a VarDecl) /// LoopName: The entire for loop (as a ForStmt) /// In the first example only: /// EndVarName: 'j' (as a VarDecl) -/// ConditionEndVarName: 'g' (as a VarDecl) /// In the second example only: /// EndCallName: 'container.size()' (as a CXXMemberCallExpr) /// /// Client code will need to make sure that: -/// - The index variables 'i', 'f', and 'h' are the same. /// - The containers on which 'size()' is called is the container indexed. /// - The index variable is only used in overloaded operator[] or /// container.at(). -/// - If the end iterator variable 'g' is defined, it is the same as 'j'. /// - The container's iterators would not be invalidated during the loop. StatementMatcher makePseudoArrayLoopMatcher() { // Test that the incoming type has a record declaration that has methods @@ -296,26 +279,20 @@ StatementMatcher makePseudoArrayLoopMatcher() { varDecl(hasInitializer(EndInitMatcher)).bind(EndVarName); StatementMatcher IndexBoundMatcher = - expr(anyOf(ignoringParenImpCasts(declRefExpr(to( - varDecl(hasType(isInteger())).bind(ConditionEndVarName)))), + expr(anyOf(ignoringParenImpCasts( + declRefExpr(to(varDecl(equalsBoundNode(EndVarName))))), EndInitMatcher)); - return forStmt( - unless(isInTemplateInstantiation()), - hasLoopInit( - anyOf(declStmt(declCountIs(2), - containsDeclaration(0, initToZeroMatcher()), - containsDeclaration(1, EndDeclMatcher)), - declStmt(hasSingleDecl(initToZeroMatcher())))), - hasCondition(anyOf( - binaryOperator(hasOperatorName("<"), - hasLHS(integerComparisonMatcher()), - hasRHS(IndexBoundMatcher)), - binaryOperator(hasOperatorName(">"), hasLHS(IndexBoundMatcher), - hasRHS(integerComparisonMatcher())))), - hasIncrement( - unaryOperator(hasOperatorName("++"), - hasUnaryOperand(incrementVarMatcher())))) + return forStmt(unless(isInTemplateInstantiation()), + hasLoopInit( + anyOf(declStmt(declCountIs(2), + containsDeclaration(0, initToZeroMatcher()), + containsDeclaration(1, EndDeclMatcher)), + declStmt(hasSingleDecl(initToZeroMatcher())))), + hasCondition(arrayConditionMatcher(IndexBoundMatcher)), + hasIncrement( + unaryOperator(hasOperatorName("++"), + hasUnaryOperand(incrementVarMatcher())))) .bind(LoopNamePseudoArray); } @@ -829,15 +806,7 @@ bool LoopConvertCheck::isConvertible(ASTContext *Context, return false; // Check that we have exactly one index variable and at most one end variable. - const auto *LoopVar = Nodes.getNodeAs(IncrementVarName); - const auto *CondVar = Nodes.getNodeAs(ConditionVarName); const auto *InitVar = Nodes.getNodeAs(InitVarName); - if (!areSameVariable(LoopVar, CondVar) || !areSameVariable(LoopVar, InitVar)) - return false; - const auto *EndVar = Nodes.getNodeAs(EndVarName); - const auto *ConditionEndVar = Nodes.getNodeAs(ConditionEndVarName); - if (EndVar && !areSameVariable(EndVar, ConditionEndVar)) - return false; // FIXME: Try to put most of this logic inside a matcher. if (FixerKind == LFK_Iterator || FixerKind == LFK_ReverseIterator) { @@ -890,7 +859,7 @@ void LoopConvertCheck::check(const MatchFinder::MatchResult &Result) { if (!isConvertible(Context, Nodes, Loop, FixerKind)) return; - const auto *LoopVar = Nodes.getNodeAs(IncrementVarName); + const auto *LoopVar = Nodes.getNodeAs(InitVarName); const auto *EndVar = Nodes.getNodeAs(EndVarName); // If the loop calls end()/size() after each iteration, lower our confidence diff --git a/clang-tools-extra/clang-tidy/modernize/ShrinkToFitCheck.cpp b/clang-tools-extra/clang-tidy/modernize/ShrinkToFitCheck.cpp index 7911a56a9a75fa4661b8c36bb2c2b29e53fbbea5..411f04eb5ee9a0adeb22e04ccae1e9465d7536a7 100644 --- a/clang-tools-extra/clang-tidy/modernize/ShrinkToFitCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/ShrinkToFitCheck.cpp @@ -21,31 +21,24 @@ namespace modernize { void ShrinkToFitCheck::registerMatchers(MatchFinder *Finder) { // Swap as a function need not to be considered, because rvalue can not // be bound to a non-const reference. - const auto ShrinkableAsMember = - memberExpr(member(valueDecl().bind("ContainerDecl"))); - const auto ShrinkableAsDecl = - declRefExpr(hasDeclaration(valueDecl().bind("ContainerDecl"))); - const auto CopyCtorCall = cxxConstructExpr(hasArgument( - 0, anyOf(ShrinkableAsMember, ShrinkableAsDecl, - unaryOperator(has(ignoringParenImpCasts(ShrinkableAsMember))), - unaryOperator(has(ignoringParenImpCasts(ShrinkableAsDecl)))))); - const auto SwapParam = - expr(anyOf(memberExpr(member(equalsBoundNode("ContainerDecl"))), - declRefExpr(hasDeclaration(equalsBoundNode("ContainerDecl"))), - unaryOperator(has(ignoringParenImpCasts( - memberExpr(member(equalsBoundNode("ContainerDecl")))))), - unaryOperator(has(ignoringParenImpCasts(declRefExpr( - hasDeclaration(equalsBoundNode("ContainerDecl")))))))); + const auto ShrinkableExpr = mapAnyOf(memberExpr, declRefExpr); + const auto Shrinkable = + ShrinkableExpr.with(hasDeclaration(valueDecl().bind("ContainerDecl"))); + const auto BoundShrinkable = ShrinkableExpr.with( + hasDeclaration(valueDecl(equalsBoundNode("ContainerDecl")))); Finder->addMatcher( cxxMemberCallExpr( - on(hasType(hasCanonicalType(hasDeclaration(namedDecl( - hasAnyName("std::basic_string", "std::deque", "std::vector")))))), callee(cxxMethodDecl(hasName("swap"))), - has(ignoringParenImpCasts( - memberExpr(traverse(TK_AsIs, hasDescendant(CopyCtorCall))))), - hasArgument(0, SwapParam.bind("ContainerToShrink")), - unless(isInTemplateInstantiation())) + hasArgument( + 0, anyOf(Shrinkable, unaryOperator(hasUnaryOperand(Shrinkable)))), + on(cxxConstructExpr(hasArgument( + 0, + expr(anyOf(BoundShrinkable, + unaryOperator(hasUnaryOperand(BoundShrinkable))), + hasType(hasCanonicalType(hasDeclaration(namedDecl(hasAnyName( + "std::basic_string", "std::deque", "std::vector")))))) + .bind("ContainerToShrink"))))) .bind("CopyAndSwapTrick"), this); } diff --git a/clang-tools-extra/clang-tidy/modernize/ShrinkToFitCheck.h b/clang-tools-extra/clang-tidy/modernize/ShrinkToFitCheck.h index 336485572bb89fd23d534afff7791f7f05ba1cc3..6ea0522d65712279063cc41769b1ca033c0d7dc0 100644 --- a/clang-tools-extra/clang-tidy/modernize/ShrinkToFitCheck.h +++ b/clang-tools-extra/clang-tidy/modernize/ShrinkToFitCheck.h @@ -30,6 +30,9 @@ public: } void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + llvm::Optional getCheckTraversalKind() const override { + return TK_IgnoreUnlessSpelledInSource; + } }; } // namespace modernize diff --git a/clang-tools-extra/clang-tidy/modernize/UseDefaultMemberInitCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseDefaultMemberInitCheck.cpp index 9ce56d953c9e661552740d3dafcf70d939cf14c7..6e7e37236b19d848f0ceb66ace2c37516241b717 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseDefaultMemberInitCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseDefaultMemberInitCheck.cpp @@ -207,14 +207,13 @@ void UseDefaultMemberInitCheck::registerMatchers(MatchFinder *Finder) { declRefExpr(to(enumConstantDecl()))); auto Init = - anyOf(initListExpr(anyOf( - allOf(initCountIs(1), hasInit(0, ignoringImplicit(InitBase))), - initCountIs(0))), + anyOf(initListExpr(anyOf(allOf(initCountIs(1), hasInit(0, InitBase)), + initCountIs(0))), InitBase); Finder->addMatcher( cxxConstructorDecl( - isDefaultConstructor(), unless(isInstantiated()), + isDefaultConstructor(), forEachConstructorInitializer( cxxCtorInitializer( forField(unless(anyOf(getLangOpts().CPlusPlus20 @@ -222,18 +221,15 @@ void UseDefaultMemberInitCheck::registerMatchers(MatchFinder *Finder) { : isBitField(), hasInClassInitializer(anything()), hasParent(recordDecl(isUnion()))))), - isWritten(), withInitializer(ignoringImplicit(Init))) + withInitializer(Init)) .bind("default"))), this); Finder->addMatcher( - cxxConstructorDecl( - unless(ast_matchers::isTemplateInstantiation()), - forEachConstructorInitializer( - cxxCtorInitializer(forField(hasInClassInitializer(anything())), - isWritten(), - withInitializer(ignoringImplicit(Init))) - .bind("existing"))), + cxxConstructorDecl(forEachConstructorInitializer( + cxxCtorInitializer(forField(hasInClassInitializer(anything())), + withInitializer(Init)) + .bind("existing"))), this); } diff --git a/clang-tools-extra/clang-tidy/modernize/UseDefaultMemberInitCheck.h b/clang-tools-extra/clang-tidy/modernize/UseDefaultMemberInitCheck.h index fc26eb55c83aee592d08d9e15e381395e875ff0d..1da57d8cd5945968eb3c286740ed4bbb3092eeae 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseDefaultMemberInitCheck.h +++ b/clang-tools-extra/clang-tidy/modernize/UseDefaultMemberInitCheck.h @@ -30,6 +30,9 @@ public: void storeOptions(ClangTidyOptions::OptionMap &Opts) override; void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + llvm::Optional getCheckTraversalKind() const override { + return TK_IgnoreUnlessSpelledInSource; + } private: void checkDefaultInit(const ast_matchers::MatchFinder::MatchResult &Result, diff --git a/clang-tools-extra/clang-tidy/modernize/UseEqualsDefaultCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseEqualsDefaultCheck.cpp index 5c79e860244aa3a00862409d4228ba2703349362..0f10dd375a80e03c98cca4286291cc27a7e92aa9 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseEqualsDefaultCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseEqualsDefaultCheck.cpp @@ -245,8 +245,6 @@ void UseEqualsDefaultCheck::registerMatchers(MatchFinder *Finder) { } void UseEqualsDefaultCheck::check(const MatchFinder::MatchResult &Result) { - std::string SpecialFunctionName; - // Both CXXConstructorDecl and CXXDestructorDecl inherit from CXXMethodDecl. const auto *SpecialFunctionDecl = Result.Nodes.getNodeAs(SpecialFunction); @@ -276,14 +274,14 @@ void UseEqualsDefaultCheck::check(const MatchFinder::MatchResult &Result) { bodyEmpty(Result.Context, Body); std::vector RemoveInitializers; - + unsigned MemberType; if (const auto *Ctor = dyn_cast(SpecialFunctionDecl)) { if (Ctor->getNumParams() == 0) { - SpecialFunctionName = "default constructor"; + MemberType = 0; } else { if (!isCopyConstructorAndCanBeDefaulted(Result.Context, Ctor)) return; - SpecialFunctionName = "copy constructor"; + MemberType = 1; // If there are constructor initializers, they must be removed. for (const auto *Init : Ctor->inits()) { RemoveInitializers.emplace_back( @@ -291,11 +289,11 @@ void UseEqualsDefaultCheck::check(const MatchFinder::MatchResult &Result) { } } } else if (isa(SpecialFunctionDecl)) { - SpecialFunctionName = "destructor"; + MemberType = 2; } else { if (!isCopyAssignmentAndCanBeDefaulted(Result.Context, SpecialFunctionDecl)) return; - SpecialFunctionName = "copy-assignment operator"; + MemberType = 3; } // The location of the body is more useful inside a macro as spelling and @@ -304,8 +302,11 @@ void UseEqualsDefaultCheck::check(const MatchFinder::MatchResult &Result) { if (Location.isMacroID()) Location = Body->getBeginLoc(); - auto Diag = diag(Location, "use '= default' to define a trivial " + - SpecialFunctionName); + auto Diag = diag( + Location, + "use '= default' to define a trivial %select{default constructor|copy " + "constructor|destructor|copy-assignment operator}0"); + Diag << MemberType; if (ApplyFix) { // Skipping comments, check for a semicolon after Body->getSourceRange() diff --git a/clang-tools-extra/clang-tidy/modernize/UseNodiscardCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseNodiscardCheck.cpp index a2e6a7a28076103453ee0c367e4732b342115f3e..f992e9de37f536711551d745306e83c85cfc7936 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseNodiscardCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseNodiscardCheck.cpp @@ -120,8 +120,8 @@ void UseNodiscardCheck::check(const MatchFinder::MatchResult &Result) { ASTContext &Context = *Result.Context; - auto Diag = diag(RetLoc, "function %0 should be marked " + NoDiscardMacro) - << MatchedDecl; + auto Diag = diag(RetLoc, "function %0 should be marked %1") + << MatchedDecl << NoDiscardMacro; // Check for the existence of the keyword being used as the ``[[nodiscard]]``. if (!doesNoDiscardMacroExist(Context, NoDiscardMacro)) diff --git a/clang-tools-extra/clang-tidy/modernize/UseTransparentFunctorsCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseTransparentFunctorsCheck.cpp index cbd11f022d59db3e7221a44432ab19760c1e5fb9..02b602ea1a0f70083559f761d563f35e9411c231 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseTransparentFunctorsCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseTransparentFunctorsCheck.cpp @@ -64,7 +64,7 @@ void UseTransparentFunctorsCheck::registerMatchers(MatchFinder *Finder) { this); } -static const StringRef Message = "prefer transparent functors '%0'"; +static const StringRef Message = "prefer transparent functors '%0<>'"; template static T getInnerTypeLocAs(TypeLoc Loc) { T Result; @@ -81,8 +81,7 @@ void UseTransparentFunctorsCheck::check( Result.Nodes.getNodeAs("FunctorClass"); if (const auto *FuncInst = Result.Nodes.getNodeAs("FuncInst")) { - diag(FuncInst->getBeginLoc(), Message) - << (FuncClass->getName() + "<>").str(); + diag(FuncInst->getBeginLoc(), Message) << FuncClass->getName(); return; } @@ -119,7 +118,7 @@ void UseTransparentFunctorsCheck::check( SourceLocation ReportLoc = FunctorLoc.getLocation(); if (ReportLoc.isInvalid()) return; - diag(ReportLoc, Message) << (FuncClass->getName() + "<>").str() + diag(ReportLoc, Message) << FuncClass->getName() << FixItHint::CreateRemoval( FunctorTypeLoc.getArgLoc(0).getSourceRange()); } diff --git a/clang-tools-extra/clang-tidy/performance/ForRangeCopyCheck.cpp b/clang-tools-extra/clang-tidy/performance/ForRangeCopyCheck.cpp index 4b7a34f6c40f52f650b67f648f72659054c7ae2e..8046301e3ce757c48167d1189730ec6394734653 100644 --- a/clang-tools-extra/clang-tidy/performance/ForRangeCopyCheck.cpp +++ b/clang-tools-extra/clang-tidy/performance/ForRangeCopyCheck.cpp @@ -45,10 +45,14 @@ void ForRangeCopyCheck::registerMatchers(MatchFinder *Finder) { hasOverloadedOperatorName("*"), callee( cxxMethodDecl(returns(unless(hasCanonicalType(referenceType())))))); + auto NotConstructedByCopy = cxxConstructExpr( + hasDeclaration(cxxConstructorDecl(unless(isCopyConstructor())))); + auto ConstructedByConversion = cxxMemberCallExpr(callee(cxxConversionDecl())); auto LoopVar = varDecl(HasReferenceOrPointerTypeOrIsAllowed, - unless(hasInitializer(expr(hasDescendant(expr(anyOf( - materializeTemporaryExpr(), IteratorReturnsValueType))))))); + unless(hasInitializer(expr(hasDescendant(expr( + anyOf(materializeTemporaryExpr(), IteratorReturnsValueType, + NotConstructedByCopy, ConstructedByConversion))))))); Finder->addMatcher( traverse(TK_AsIs, cxxForRangeStmt(hasLoopVariable(LoopVar.bind("loopVar"))) diff --git a/clang-tools-extra/clang-tidy/performance/MoveConstructorInitCheck.cpp b/clang-tools-extra/clang-tidy/performance/MoveConstructorInitCheck.cpp index 34d8e720629098da7c24865bcad10166052ab657..dd6e5a816ded1d363af61f23dd8a0ac1336061e3 100644 --- a/clang-tools-extra/clang-tidy/performance/MoveConstructorInitCheck.cpp +++ b/clang-tools-extra/clang-tidy/performance/MoveConstructorInitCheck.cpp @@ -74,8 +74,9 @@ void MoveConstructorInitCheck::check(const MatchFinder::MatchResult &Result) { // There's a move constructor candidate that the caller probably intended // to call instead. diag(Initializer->getSourceLocation(), - "move constructor initializes %0 by calling a copy constructor") - << (Initializer->isBaseInitializer() ? "base class" : "class member"); + "move constructor initializes %select{class member|base class}0 by " + "calling a copy constructor") + << Initializer->isBaseInitializer(); diag(CopyCtor->getLocation(), "copy constructor being called", DiagnosticIDs::Note); diag(Candidate->getLocation(), "candidate move constructor here", diff --git a/clang-tools-extra/clang-tidy/performance/NoexceptMoveConstructorCheck.cpp b/clang-tools-extra/clang-tidy/performance/NoexceptMoveConstructorCheck.cpp index d23f0ab0cf44d8227580533e14d6dd29244ee35d..f87f214945fa233f11240c4872cce9251abbdd41 100644 --- a/clang-tools-extra/clang-tidy/performance/NoexceptMoveConstructorCheck.cpp +++ b/clang-tools-extra/clang-tidy/performance/NoexceptMoveConstructorCheck.cpp @@ -29,11 +29,11 @@ void NoexceptMoveConstructorCheck::registerMatchers(MatchFinder *Finder) { void NoexceptMoveConstructorCheck::check( const MatchFinder::MatchResult &Result) { if (const auto *Decl = Result.Nodes.getNodeAs("decl")) { - StringRef MethodType = "assignment operator"; + bool IsConstructor = false; if (const auto *Ctor = dyn_cast(Decl)) { if (!Ctor->isMoveConstructor()) return; - MethodType = "constructor"; + IsConstructor = true; } else if (!Decl->isMoveAssignmentOperator()) { return; } @@ -44,9 +44,10 @@ void NoexceptMoveConstructorCheck::check( return; if (!isNoexceptExceptionSpec(ProtoType->getExceptionSpecType())) { - auto Diag = - diag(Decl->getLocation(), "move %0s should be marked noexcept") - << MethodType; + auto Diag = diag(Decl->getLocation(), + "move %select{assignment operator|constructor}0s should " + "be marked noexcept") + << IsConstructor; // Add FixIt hints. SourceManager &SM = *Result.SourceManager; assert(Decl->getNumParams() > 0); @@ -68,8 +69,9 @@ void NoexceptMoveConstructorCheck::check( E = E->IgnoreImplicit(); if (!isa(E)) { diag(E->getExprLoc(), - "noexcept specifier on the move %0 evaluates to 'false'") - << MethodType; + "noexcept specifier on the move %select{assignment " + "operator|constructor}0 evaluates to 'false'") + << IsConstructor; } } } diff --git a/clang-tools-extra/clang-tidy/performance/UnnecessaryCopyInitialization.cpp b/clang-tools-extra/clang-tidy/performance/UnnecessaryCopyInitialization.cpp index f63dbab093581b016528bbbd0124d48b3af0c458..0cc9a44263021838ac5480052410354e7ac55b33 100644 --- a/clang-tools-extra/clang-tidy/performance/UnnecessaryCopyInitialization.cpp +++ b/clang-tools-extra/clang-tidy/performance/UnnecessaryCopyInitialization.cpp @@ -206,13 +206,10 @@ void UnnecessaryCopyInitialization::handleCopyFromMethodReturn( auto Diagnostic = diag(Var.getLocation(), - IsConstQualified ? "the const qualified variable %0 is " - "copy-constructed from a const reference; " - "consider making it a const reference" - : "the variable %0 is copy-constructed from a " - "const reference but is only used as const " - "reference; consider making it a const reference") - << &Var; + "the %select{|const qualified }0variable %1 is copy-constructed " + "from a const reference%select{ but is only used as const " + "reference|}0; consider making it a const reference") + << IsConstQualified << &Var; if (IssueFix) recordFixes(Var, Context, Diagnostic); } diff --git a/clang-tools-extra/clang-tidy/performance/UnnecessaryValueParamCheck.cpp b/clang-tools-extra/clang-tidy/performance/UnnecessaryValueParamCheck.cpp index e3dae9a135581d2160052987c0152fd99e3fb522..2a9f9b70517758cbd276796e047af3cd08b92dc5 100644 --- a/clang-tools-extra/clang-tidy/performance/UnnecessaryValueParamCheck.cpp +++ b/clang-tools-extra/clang-tidy/performance/UnnecessaryValueParamCheck.cpp @@ -137,13 +137,10 @@ void UnnecessaryValueParamCheck::check(const MatchFinder::MatchResult &Result) { auto Diag = diag(Param->getLocation(), - IsConstQualified ? "the const qualified parameter %0 is " - "copied for each invocation; consider " - "making it a reference" - : "the parameter %0 is copied for each " - "invocation but only used as a const reference; " - "consider making it a const reference") - << paramNameOrIndex(Param->getName(), Index); + "the %select{|const qualified }0parameter %1 is copied for each " + "invocation%select{ but only used as a const reference|}0; consider " + "making it a %select{const |}0reference") + << IsConstQualified << paramNameOrIndex(Param->getName(), Index); // Do not propose fixes when: // 1. the ParmVarDecl is in a macro, since we cannot place them correctly // 2. the function is virtual as it might break overrides diff --git a/clang-tools-extra/clang-tidy/portability/SIMDIntrinsicsCheck.cpp b/clang-tools-extra/clang-tidy/portability/SIMDIntrinsicsCheck.cpp index 65d7c1746b18f571d13dc5a4731383ee08c0cd03..8ea00a0b8ed6ad61f94c73147637ea4c9c697538 100644 --- a/clang-tools-extra/clang-tidy/portability/SIMDIntrinsicsCheck.cpp +++ b/clang-tools-extra/clang-tidy/portability/SIMDIntrinsicsCheck.cpp @@ -12,6 +12,7 @@ #include "clang/Basic/TargetInfo.h" #include "llvm/ADT/StringMap.h" #include "llvm/ADT/Triple.h" +#include "llvm/Support/ManagedStatic.h" #include "llvm/Support/Regex.h" using namespace clang::ast_matchers; @@ -130,20 +131,18 @@ void SIMDIntrinsicsCheck::check(const MatchFinder::MatchResult &Result) { // We have found a std::simd replacement. if (!New.empty()) { - std::string Message; // If Suggest is true, give a P0214 alternative, otherwise point it out it // is non-portable. if (Suggest) { - Message = (Twine("'") + Old + "' can be replaced by " + New).str(); - Message = llvm::Regex("\\$std").sub(Std, Message); - Message = - llvm::Regex("\\$simd").sub((Std.str() + "::simd").str(), Message); + static const llvm::Regex StdRegex("\\$std"), SimdRegex("\\$simd"); + diag(Call->getExprLoc(), "'%0' can be replaced by %1") + << Old + << SimdRegex.sub(SmallString<32>({Std, "::simd"}), + StdRegex.sub(Std, New)); } else { - Message = (Twine("'") + Old + "' is a non-portable " + - llvm::Triple::getArchTypeName(Arch) + " intrinsic function") - .str(); + diag("'%0' is a non-portable %1 intrinsic function") + << Old << llvm::Triple::getArchTypeName(Arch); } - diag(Call->getExprLoc(), Message); } } diff --git a/clang-tools-extra/clang-tidy/readability/FunctionCognitiveComplexityCheck.cpp b/clang-tools-extra/clang-tidy/readability/FunctionCognitiveComplexityCheck.cpp index 3a4758302406f0c74439412dbc822c266703f2f2..94ff38dcec05e86a8013d4829807ebd927629e3d 100644 --- a/clang-tools-extra/clang-tidy/readability/FunctionCognitiveComplexityCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/FunctionCognitiveComplexityCheck.cpp @@ -492,11 +492,13 @@ public: FunctionCognitiveComplexityCheck::FunctionCognitiveComplexityCheck( StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context), - Threshold(Options.get("Threshold", CognitiveComplexity::DefaultLimit)) {} + Threshold(Options.get("Threshold", CognitiveComplexity::DefaultLimit)), + DescribeBasicIncrements(Options.get("DescribeBasicIncrements", true)) {} void FunctionCognitiveComplexityCheck::storeOptions( ClangTidyOptions::OptionMap &Opts) { Options.store(Opts, "Threshold", Threshold); + Options.store(Opts, "DescribeBasicIncrements", DescribeBasicIncrements); } void FunctionCognitiveComplexityCheck::registerMatchers(MatchFinder *Finder) { @@ -537,6 +539,9 @@ void FunctionCognitiveComplexityCheck::check( diag(Loc, "lambda has cognitive complexity of %0 (threshold %1)") << Visitor.CC.Total << Threshold; + if (!DescribeBasicIncrements) + return; + // Output all the basic increments of complexity. for (const auto &Detail : Visitor.CC.Details) { unsigned MsgId; // The id of the message to output. diff --git a/clang-tools-extra/clang-tidy/readability/FunctionCognitiveComplexityCheck.h b/clang-tools-extra/clang-tidy/readability/FunctionCognitiveComplexityCheck.h index a21b8447029b26434b5ded0df9659dbf1634b84c..01244ab0ecf05911b79bb5a836346657dcbbf463 100644 --- a/clang-tools-extra/clang-tidy/readability/FunctionCognitiveComplexityCheck.h +++ b/clang-tools-extra/clang-tidy/readability/FunctionCognitiveComplexityCheck.h @@ -17,10 +17,15 @@ namespace readability { /// Checks function Cognitive Complexity metric. /// -/// There is only one configuration option: +/// There are the following configuration option: /// /// * `Threshold` - flag functions with Cognitive Complexity exceeding /// this number. The default is `25`. +/// * `DescribeBasicIncrements`- if set to `true`, then for each function +/// exceeding the complexity threshold the check will issue additional +/// diagnostics on every piece of code (loop, `if` statement, etc.) which +/// contributes to that complexity. +// Default is `true` /// /// For the user-facing documentation see: /// http://clang.llvm.org/extra/clang-tidy/checks/readability-function-cognitive-complexity.html @@ -37,6 +42,7 @@ public: private: const unsigned Threshold; + const bool DescribeBasicIncrements; }; } // namespace readability diff --git a/clang-tools-extra/clang-tidy/readability/IdentifierNamingCheck.cpp b/clang-tools-extra/clang-tidy/readability/IdentifierNamingCheck.cpp index fccff502e1833ef0cc751a500d6ffdc2a6f9817f..09b3cc25678c60b29ace2eb55108b2fee1afa94e 100644 --- a/clang-tools-extra/clang-tidy/readability/IdentifierNamingCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/IdentifierNamingCheck.cpp @@ -157,7 +157,7 @@ getFileStyleFromOptions(const ClangTidyCheck::OptionsView &Options) { StyleString.pop_back(); StyleString.pop_back(); auto CaseOptional = - Options.getOptional(StyleString); + Options.get(StyleString); if (CaseOptional || !Prefix.empty() || !Postfix.empty() || !IgnoredRegexpStr.empty()) diff --git a/clang-tools-extra/clang-tidy/readability/InconsistentDeclarationParameterNameCheck.cpp b/clang-tools-extra/clang-tidy/readability/InconsistentDeclarationParameterNameCheck.cpp index 43d2f6a69cd13922c4238d66de0bce31cbcf0948..c28424b11f279976b9ca941784999552122119cc 100644 --- a/clang-tools-extra/clang-tidy/readability/InconsistentDeclarationParameterNameCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/InconsistentDeclarationParameterNameCheck.cpp @@ -12,7 +12,6 @@ #include #include -#include using namespace clang::ast_matchers; diff --git a/clang-tools-extra/clang-tidy/readability/QualifiedAutoCheck.cpp b/clang-tools-extra/clang-tidy/readability/QualifiedAutoCheck.cpp index 9ca4d2e1ad4deb17ceaf21f6cd534b4fcacfb5c8..f2838cd0b984142794f5c7b9857706a12453b87c 100644 --- a/clang-tools-extra/clang-tidy/readability/QualifiedAutoCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/QualifiedAutoCheck.cpp @@ -209,10 +209,11 @@ void QualifiedAutoCheck::check(const MatchFinder::MatchResult &Result) { }(); DiagnosticBuilder Diag = - diag(FixitLoc, "'%0%1%2auto %3' can be declared as '%4%3'") - << (IsLocalConst ? "const " : "") - << (IsLocalVolatile ? "volatile " : "") - << (IsLocalRestrict ? "__restrict " : "") << Var->getName() << ReplStr; + diag(FixitLoc, + "'%select{|const }0%select{|volatile }1%select{|__restrict }2auto " + "%3' can be declared as '%4%3'") + << IsLocalConst << IsLocalVolatile << IsLocalRestrict << Var->getName() + << ReplStr; for (SourceRange &Range : RemoveQualifiersRange) { Diag << FixItHint::CreateRemoval(CharSourceRange::getCharRange(Range)); @@ -253,10 +254,12 @@ void QualifiedAutoCheck::check(const MatchFinder::MatchResult &Result) { TypeSpec->getEnd().isMacroID()) return; SourceLocation InsertPos = TypeSpec->getBegin(); - diag(InsertPos, "'auto *%0%1%2' can be declared as 'const auto *%0%1%2'") - << (Var->getType().isLocalConstQualified() ? "const " : "") - << (Var->getType().isLocalVolatileQualified() ? "volatile " : "") - << Var->getName() << FixItHint::CreateInsertion(InsertPos, "const "); + diag(InsertPos, + "'auto *%select{|const }0%select{|volatile }1%2' can be declared as " + "'const auto *%select{|const }0%select{|volatile }1%2'") + << Var->getType().isLocalConstQualified() + << Var->getType().isLocalVolatileQualified() << Var->getName() + << FixItHint::CreateInsertion(InsertPos, "const "); } return; } diff --git a/clang-tools-extra/clang-tidy/readability/RedundantMemberInitCheck.cpp b/clang-tools-extra/clang-tidy/readability/RedundantMemberInitCheck.cpp index df6e57bfb9eda2315f473f809a6182966b6363ef..0af9a0478ab9bc910e808fabd21292705287a005 100644 --- a/clang-tools-extra/clang-tidy/readability/RedundantMemberInitCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/RedundantMemberInitCheck.cpp @@ -26,26 +26,21 @@ void RedundantMemberInitCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { } void RedundantMemberInitCheck::registerMatchers(MatchFinder *Finder) { - auto Construct = - cxxConstructExpr( - hasDeclaration(cxxConstructorDecl(hasParent( - cxxRecordDecl(unless(isTriviallyDefaultConstructible())))))) - .bind("construct"); - Finder->addMatcher( - traverse( - TK_AsIs, - cxxConstructorDecl( - unless(isDelegatingConstructor()), - ofClass(unless( - anyOf(isUnion(), ast_matchers::isTemplateInstantiation()))), - forEachConstructorInitializer( - cxxCtorInitializer( - isWritten(), withInitializer(ignoringImplicit(Construct)), - unless(forField(hasType(isConstQualified()))), - unless(forField(hasParent(recordDecl(isUnion()))))) - .bind("init"))) - .bind("constructor")), + cxxConstructorDecl( + unless(isDelegatingConstructor()), ofClass(unless(isUnion())), + forEachConstructorInitializer( + cxxCtorInitializer( + withInitializer( + cxxConstructExpr( + hasDeclaration( + cxxConstructorDecl(ofClass(cxxRecordDecl( + unless(isTriviallyDefaultConstructible())))))) + .bind("construct")), + unless(forField(hasType(isConstQualified()))), + unless(forField(hasParent(recordDecl(isUnion()))))) + .bind("init"))) + .bind("constructor"), this); } diff --git a/clang-tools-extra/clang-tidy/readability/RedundantMemberInitCheck.h b/clang-tools-extra/clang-tidy/readability/RedundantMemberInitCheck.h index 9454278ca244dd1487ea193f1dcefcc830346ddf..6d40a94f59cad92236e4b3118fff125741d466c0 100644 --- a/clang-tools-extra/clang-tidy/readability/RedundantMemberInitCheck.h +++ b/clang-tools-extra/clang-tidy/readability/RedundantMemberInitCheck.h @@ -32,6 +32,9 @@ public: void storeOptions(ClangTidyOptions::OptionMap &Opts) override; void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + llvm::Optional getCheckTraversalKind() const override { + return TK_IgnoreUnlessSpelledInSource; + } private: bool IgnoreBaseInCopyConstructors; diff --git a/clang-tools-extra/clang-tidy/readability/RedundantSmartptrGetCheck.cpp b/clang-tools-extra/clang-tidy/readability/RedundantSmartptrGetCheck.cpp index bf78acdc5f685553f2df39ba2cff94d38de153cf..e663f055f30d261094d85dd9f30de5419e19edf0 100644 --- a/clang-tools-extra/clang-tidy/readability/RedundantSmartptrGetCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/RedundantSmartptrGetCheck.cpp @@ -18,15 +18,30 @@ namespace readability { namespace { internal::Matcher callToGet(const internal::Matcher &OnClass) { - return cxxMemberCallExpr( - on(expr(anyOf(hasType(OnClass), - hasType(qualType( - pointsTo(decl(OnClass).bind("ptr_to_ptr")))))) - .bind("smart_pointer")), - unless(callee(memberExpr(hasObjectExpression(cxxThisExpr())))), - callee(cxxMethodDecl( - hasName("get"), - returns(qualType(pointsTo(type().bind("getType"))))))) + return expr( + anyOf(cxxMemberCallExpr( + on(expr(anyOf(hasType(OnClass), + hasType(qualType(pointsTo( + decl(OnClass).bind("ptr_to_ptr")))))) + .bind("smart_pointer")), + unless(callee( + memberExpr(hasObjectExpression(cxxThisExpr())))), + callee(cxxMethodDecl(hasName("get"), + returns(qualType(pointsTo( + type().bind("getType"))))))), + cxxDependentScopeMemberExpr( + hasMemberName("get"), + hasObjectExpression( + expr(hasType(qualType(hasCanonicalType( + templateSpecializationType(hasDeclaration( + classTemplateDecl(has(cxxRecordDecl( + OnClass, + hasMethod(cxxMethodDecl( + hasName("get"), + returns(qualType( + pointsTo(type().bind( + "getType"))))))))))))))) + .bind("smart_pointer"))))) .bind("redundant_get"); } @@ -47,10 +62,9 @@ void registerMatchersForGetArrowStart(MatchFinder *Finder, const auto Smartptr = anyOf(knownSmartptr(), QuacksLikeASmartptr); // Catch 'ptr.get()->Foo()' - Finder->addMatcher( - memberExpr(expr().bind("memberExpr"), isArrow(), - hasObjectExpression(ignoringImpCasts(callToGet(Smartptr)))), - Callback); + Finder->addMatcher(memberExpr(expr().bind("memberExpr"), isArrow(), + hasObjectExpression(callToGet(Smartptr))), + Callback); // Catch '*ptr.get()' or '*ptr->get()' Finder->addMatcher( @@ -58,8 +72,8 @@ void registerMatchersForGetArrowStart(MatchFinder *Finder, Callback); // Catch '!ptr.get()' - const auto CallToGetAsBool = ignoringParenImpCasts(callToGet( - recordDecl(Smartptr, has(cxxConversionDecl(returns(booleanType())))))); + const auto CallToGetAsBool = callToGet( + recordDecl(Smartptr, has(cxxConversionDecl(returns(booleanType()))))); Finder->addMatcher( unaryOperator(hasOperatorName("!"), hasUnaryOperand(CallToGetAsBool)), Callback); @@ -70,6 +84,10 @@ void registerMatchersForGetArrowStart(MatchFinder *Finder, // Catch 'ptr.get() ? X : Y' Finder->addMatcher(conditionalOperator(hasCondition(CallToGetAsBool)), Callback); + + Finder->addMatcher(cxxDependentScopeMemberExpr(hasObjectExpression( + callExpr(has(callToGet(Smartptr))).bind("obj"))), + Callback); } void registerMatchersForGetEquals(MatchFinder *Finder, @@ -82,9 +100,8 @@ void registerMatchersForGetEquals(MatchFinder *Finder, // Matches against nullptr. Finder->addMatcher( binaryOperator(hasAnyOperatorName("==", "!="), - hasOperands(ignoringImpCasts(anyOf( - cxxNullPtrLiteralExpr(), gnuNullExpr(), - integerLiteral(equals(0)))), + hasOperands(anyOf(cxxNullPtrLiteralExpr(), gnuNullExpr(), + integerLiteral(equals(0))), callToGet(knownSmartptr()))), Callback); @@ -138,13 +155,21 @@ void RedundantSmartptrGetCheck::check(const MatchFinder::MatchResult &Result) { return; } + auto SR = GetCall->getSourceRange(); + // CXXDependentScopeMemberExpr source range does not include parens + // Extend the source range of the get call to account for them. + if (isa(GetCall)) + SR.setEnd(Lexer::getLocForEndOfToken(SR.getEnd(), 0, *Result.SourceManager, + getLangOpts()) + .getLocWithOffset(1)); + StringRef SmartptrText = Lexer::getSourceText( CharSourceRange::getTokenRange(Smartptr->getSourceRange()), *Result.SourceManager, getLangOpts()); // Replace foo->get() with *foo, and foo.get() with foo. std::string Replacement = Twine(IsPtrToPtr ? "*" : "", SmartptrText).str(); diag(GetCall->getBeginLoc(), "redundant get() call on smart pointer") - << FixItHint::CreateReplacement(GetCall->getSourceRange(), Replacement); + << FixItHint::CreateReplacement(SR, Replacement); } } // namespace readability diff --git a/clang-tools-extra/clang-tidy/readability/RedundantSmartptrGetCheck.h b/clang-tools-extra/clang-tidy/readability/RedundantSmartptrGetCheck.h index 3214863602ca5a7056a2e83ea19328ad23234a15..37c09b871d44cc7ad30a4f4de6b1e6ba6796a890 100644 --- a/clang-tools-extra/clang-tidy/readability/RedundantSmartptrGetCheck.h +++ b/clang-tools-extra/clang-tidy/readability/RedundantSmartptrGetCheck.h @@ -35,6 +35,9 @@ public: void storeOptions(ClangTidyOptions::OptionMap &Opts) override; void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + llvm::Optional getCheckTraversalKind() const override { + return TK_IgnoreUnlessSpelledInSource; + } private: const bool IgnoreMacros; diff --git a/clang-tools-extra/clang-tidy/readability/SimplifyBooleanExprCheck.cpp b/clang-tools-extra/clang-tidy/readability/SimplifyBooleanExprCheck.cpp index d450df55c6a0dd892896fc8e41961a5fb424a34f..4ea8ef65d3f8d588712f31f7252668126d9db409 100644 --- a/clang-tools-extra/clang-tidy/readability/SimplifyBooleanExprCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/SimplifyBooleanExprCheck.cpp @@ -71,10 +71,10 @@ const Expr *getBoolLiteral(const MatchFinder::MatchResult &Result, } internal::BindableMatcher literalOrNegatedBool(bool Value) { - return expr(anyOf(cxxBoolLiteral(equals(Value)), - unaryOperator(hasUnaryOperand(ignoringParenImpCasts( - cxxBoolLiteral(equals(!Value)))), - hasOperatorName("!")))); + return expr( + anyOf(cxxBoolLiteral(equals(Value)), + unaryOperator(hasUnaryOperand(cxxBoolLiteral(equals(!Value))), + hasOperatorName("!")))); } internal::Matcher returnsBool(bool Value, StringRef Id = "ignored") { @@ -443,8 +443,7 @@ void SimplifyBooleanExprCheck::matchBoolCondition(MatchFinder *Finder, bool Value, StringRef BooleanId) { Finder->addMatcher( - ifStmt(unless(isInTemplateInstantiation()), - hasCondition(literalOrNegatedBool(Value).bind(BooleanId))) + ifStmt(hasCondition(literalOrNegatedBool(Value).bind(BooleanId))) .bind(IfStmtId), this); } @@ -453,8 +452,7 @@ void SimplifyBooleanExprCheck::matchTernaryResult(MatchFinder *Finder, bool Value, StringRef TernaryId) { Finder->addMatcher( - conditionalOperator(unless(isInTemplateInstantiation()), - hasTrueExpression(literalOrNegatedBool(Value)), + conditionalOperator(hasTrueExpression(literalOrNegatedBool(Value)), hasFalseExpression(literalOrNegatedBool(!Value))) .bind(TernaryId), this); @@ -463,14 +461,12 @@ void SimplifyBooleanExprCheck::matchTernaryResult(MatchFinder *Finder, void SimplifyBooleanExprCheck::matchIfReturnsBool(MatchFinder *Finder, bool Value, StringRef Id) { if (ChainedConditionalReturn) - Finder->addMatcher(ifStmt(unless(isInTemplateInstantiation()), - hasThen(returnsBool(Value, ThenLiteralId)), + Finder->addMatcher(ifStmt(hasThen(returnsBool(Value, ThenLiteralId)), hasElse(returnsBool(!Value))) .bind(Id), this); else - Finder->addMatcher(ifStmt(unless(isInTemplateInstantiation()), - unless(hasParent(ifStmt())), + Finder->addMatcher(ifStmt(unless(hasParent(ifStmt())), hasThen(returnsBool(Value, ThenLiteralId)), hasElse(returnsBool(!Value))) .bind(Id), @@ -495,16 +491,12 @@ void SimplifyBooleanExprCheck::matchIfAssignsBool(MatchFinder *Finder, auto Else = anyOf(SimpleElse, compoundStmt(statementCountIs(1), hasAnySubstatement(SimpleElse))); if (ChainedConditionalAssignment) - Finder->addMatcher(ifStmt(unless(isInTemplateInstantiation()), - hasThen(Then), hasElse(Else)) - .bind(Id), - this); + Finder->addMatcher(ifStmt(hasThen(Then), hasElse(Else)).bind(Id), this); else - Finder->addMatcher(ifStmt(unless(isInTemplateInstantiation()), - unless(hasParent(ifStmt())), hasThen(Then), - hasElse(Else)) - .bind(Id), - this); + Finder->addMatcher( + ifStmt(unless(hasParent(ifStmt())), hasThen(Then), hasElse(Else)) + .bind(Id), + this); } void SimplifyBooleanExprCheck::matchCompoundIfReturnsBool(MatchFinder *Finder, @@ -512,11 +504,9 @@ void SimplifyBooleanExprCheck::matchCompoundIfReturnsBool(MatchFinder *Finder, StringRef Id) { Finder->addMatcher( compoundStmt( - unless(isInTemplateInstantiation()), hasAnySubstatement( ifStmt(hasThen(returnsBool(Value)), unless(hasElse(stmt())))), - hasAnySubstatement(returnStmt(has(ignoringParenImpCasts( - literalOrNegatedBool(!Value)))) + hasAnySubstatement(returnStmt(has(literalOrNegatedBool(!Value))) .bind(CompoundReturnId))) .bind(Id), this); diff --git a/clang-tools-extra/clang-tidy/readability/SimplifyBooleanExprCheck.h b/clang-tools-extra/clang-tidy/readability/SimplifyBooleanExprCheck.h index af9017d4d3ae17d8282c62ae27b8435f2fa32bac..626108cbe22c92ad1ecfd6a31816d46696b92054 100644 --- a/clang-tools-extra/clang-tidy/readability/SimplifyBooleanExprCheck.h +++ b/clang-tools-extra/clang-tidy/readability/SimplifyBooleanExprCheck.h @@ -27,6 +27,9 @@ public: void storeOptions(ClangTidyOptions::OptionMap &Options) override; void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + llvm::Optional getCheckTraversalKind() const override { + return TK_IgnoreUnlessSpelledInSource; + } private: class Visitor; diff --git a/clang-tools-extra/clang-tidy/readability/UniqueptrDeleteReleaseCheck.cpp b/clang-tools-extra/clang-tidy/readability/UniqueptrDeleteReleaseCheck.cpp index 478a3f2f93ba938a72e12665e6d4065ce7a011c1..45194a8e3d97ebb5a647cf51e34263a97f674c56 100644 --- a/clang-tools-extra/clang-tidy/readability/UniqueptrDeleteReleaseCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/UniqueptrDeleteReleaseCheck.cpp @@ -9,6 +9,8 @@ #include "UniqueptrDeleteReleaseCheck.h" #include "clang/AST/ASTContext.h" #include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/SourceLocation.h" #include "clang/Lex/Lexer.h" using namespace clang::ast_matchers; @@ -17,50 +19,69 @@ namespace clang { namespace tidy { namespace readability { +void UniqueptrDeleteReleaseCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "PreferResetCall", PreferResetCall); +} + +UniqueptrDeleteReleaseCheck::UniqueptrDeleteReleaseCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + PreferResetCall(Options.get("PreferResetCall", false)) {} + void UniqueptrDeleteReleaseCheck::registerMatchers(MatchFinder *Finder) { - auto IsSusbstituted = qualType(anyOf( - substTemplateTypeParmType(), hasDescendant(substTemplateTypeParmType()))); auto UniquePtrWithDefaultDelete = classTemplateSpecializationDecl( - hasName("std::unique_ptr"), - hasTemplateArgument(1, refersToType(qualType(hasDeclaration(cxxRecordDecl( - hasName("std::default_delete"))))))); + hasName("::std::unique_ptr"), + hasTemplateArgument(1, refersToType(hasDeclaration(cxxRecordDecl( + hasName("::std::default_delete")))))); Finder->addMatcher( - cxxDeleteExpr(has(ignoringParenImpCasts(cxxMemberCallExpr( - on(expr(hasType(UniquePtrWithDefaultDelete), - unless(hasType(IsSusbstituted))) - .bind("uptr")), - callee(cxxMethodDecl(hasName("release"))))))) + cxxDeleteExpr( + unless(isInTemplateInstantiation()), + has(expr(ignoringParenImpCasts( + cxxMemberCallExpr( + callee( + memberExpr(hasObjectExpression(allOf( + unless(isTypeDependent()), + anyOf(hasType(UniquePtrWithDefaultDelete), + hasType(pointsTo( + UniquePtrWithDefaultDelete))))), + member(cxxMethodDecl(hasName("release")))) + .bind("release_expr"))) + .bind("release_call"))))) .bind("delete"), this); } void UniqueptrDeleteReleaseCheck::check( const MatchFinder::MatchResult &Result) { - const auto *PtrExpr = Result.Nodes.getNodeAs("uptr"); - const auto *DeleteExpr = Result.Nodes.getNodeAs("delete"); + const auto *DeleteExpr = Result.Nodes.getNodeAs("delete"); + const auto *ReleaseExpr = Result.Nodes.getNodeAs("release_expr"); + const auto *ReleaseCallExpr = + Result.Nodes.getNodeAs("release_call"); - if (PtrExpr->getBeginLoc().isMacroID()) + if (ReleaseExpr->getBeginLoc().isMacroID()) return; - // Ignore dependent types. - // It can give us false positives, so we go with false negatives instead to - // be safe. - if (PtrExpr->getType()->isDependentType()) - return; - - SourceLocation AfterPtr = Lexer::getLocForEndOfToken( - PtrExpr->getEndLoc(), 0, *Result.SourceManager, getLangOpts()); - - diag(DeleteExpr->getBeginLoc(), - "prefer '= nullptr' to 'delete x.release()' to reset unique_ptr<> " - "objects") - << FixItHint::CreateRemoval(CharSourceRange::getCharRange( - DeleteExpr->getBeginLoc(), PtrExpr->getBeginLoc())) - << FixItHint::CreateReplacement( - CharSourceRange::getTokenRange(AfterPtr, DeleteExpr->getEndLoc()), - " = nullptr"); + auto D = + diag(DeleteExpr->getBeginLoc(), "prefer '%select{= nullptr|reset()}0' " + "to reset 'unique_ptr<>' objects"); + D << PreferResetCall << DeleteExpr->getSourceRange() + << FixItHint::CreateRemoval(CharSourceRange::getCharRange( + DeleteExpr->getBeginLoc(), + DeleteExpr->getArgument()->getBeginLoc())); + if (PreferResetCall) { + D << FixItHint::CreateReplacement(ReleaseExpr->getMemberLoc(), "reset"); + } else { + if (ReleaseExpr->isArrow()) + D << FixItHint::CreateInsertion(ReleaseExpr->getBase()->getBeginLoc(), + "*"); + D << FixItHint::CreateReplacement( + CharSourceRange::getTokenRange(ReleaseExpr->getOperatorLoc(), + ReleaseCallExpr->getEndLoc()), + " = nullptr"); + } } } // namespace readability diff --git a/clang-tools-extra/clang-tidy/readability/UniqueptrDeleteReleaseCheck.h b/clang-tools-extra/clang-tidy/readability/UniqueptrDeleteReleaseCheck.h index 3e6f184b58b808833a911c54a255d909475db6cc..88bb82539ac508eaf98907f708230f522290e02c 100644 --- a/clang-tools-extra/clang-tidy/readability/UniqueptrDeleteReleaseCheck.h +++ b/clang-tools-extra/clang-tidy/readability/UniqueptrDeleteReleaseCheck.h @@ -22,10 +22,13 @@ namespace readability { /// http://clang.llvm.org/extra/clang-tidy/checks/readability-uniqueptr-delete-release.html class UniqueptrDeleteReleaseCheck : public ClangTidyCheck { public: - UniqueptrDeleteReleaseCheck(StringRef Name, ClangTidyContext *Context) - : ClangTidyCheck(Name, Context) {} + UniqueptrDeleteReleaseCheck(StringRef Name, ClangTidyContext *Context); void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + +private: + const bool PreferResetCall; }; } // namespace readability diff --git a/clang-tools-extra/clang-tidy/readability/UseAnyOfAllOfCheck.cpp b/clang-tools-extra/clang-tidy/readability/UseAnyOfAllOfCheck.cpp index 6e837509708b0962dd5cafc68d348573aad7e19c..495b2fa0b185f7970b3d538561208f4435f9eb93 100644 --- a/clang-tools-extra/clang-tidy/readability/UseAnyOfAllOfCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/UseAnyOfAllOfCheck.cpp @@ -88,19 +88,20 @@ static bool isViableLoop(const CXXForRangeStmt &S, ASTContext &Context) { } void UseAnyOfAllOfCheck::check(const MatchFinder::MatchResult &Result) { - StringRef Ranges = getLangOpts().CPlusPlus20 ? "::ranges" : ""; if (const auto *S = Result.Nodes.getNodeAs("any_of_loop")) { if (!isViableLoop(*S, *Result.Context)) return; - diag(S->getForLoc(), "replace loop by 'std%0::any_of()'") << Ranges; + diag(S->getForLoc(), "replace loop by 'std%select{|::ranges}0::any_of()'") + << getLangOpts().CPlusPlus20; } else if (const auto *S = Result.Nodes.getNodeAs("all_of_loop")) { if (!isViableLoop(*S, *Result.Context)) return; - diag(S->getForLoc(), "replace loop by 'std%0::all_of()'") << Ranges; + diag(S->getForLoc(), "replace loop by 'std%select{|::ranges}0::all_of()'") + << getLangOpts().CPlusPlus20; } } diff --git a/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp b/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp index 2466b647c68c88a6c4581d86b425bb0186f15b86..6147d90eb10b9718b0f37562d96e40e5bc9747f6 100644 --- a/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp +++ b/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp @@ -127,6 +127,15 @@ well. )"), cl::init(false), cl::cat(ClangTidyCategory)); +static cl::opt FixNotes("fix-notes", cl::desc(R"( +If a warning has no fix, but a single fix can +be found through an associated diagnostic note, +apply the fix. +Specifying this flag will implicitly enable the +'--fix' flag. +)"), + cl::init(false), cl::cat(ClangTidyCategory)); + static cl::opt FormatStyle("format-style", cl::desc(R"( Style for formatting code around applied fixes: - 'none' (default) turns off formatting @@ -483,18 +492,22 @@ int clangTidyMain(int argc, const char **argv) { AllowEnablingAnalyzerAlphaCheckers); std::vector Errors = runClangTidy(Context, OptionsParser->getCompilations(), PathList, BaseFS, - EnableCheckProfile, ProfilePrefix); + FixNotes, EnableCheckProfile, ProfilePrefix); bool FoundErrors = llvm::find_if(Errors, [](const ClangTidyError &E) { return E.DiagLevel == ClangTidyError::Error; }) != Errors.end(); - const bool DisableFixes = Fix && FoundErrors && !FixErrors; + // --fix-errors and --fix-notes imply --fix. + FixBehaviour Behaviour = FixNotes ? FB_FixNotes + : (Fix || FixErrors) ? FB_Fix + : FB_NoFix; + + const bool DisableFixes = FoundErrors && !FixErrors; unsigned WErrorCount = 0; - // -fix-errors implies -fix. - handleErrors(Errors, Context, (FixErrors || Fix) && !DisableFixes, WErrorCount, - BaseFS); + handleErrors(Errors, Context, DisableFixes ? FB_NoFix : Behaviour, + WErrorCount, BaseFS); if (!ExportFixes.empty() && !Errors.empty()) { std::error_code EC; @@ -508,7 +521,7 @@ int clangTidyMain(int argc, const char **argv) { if (!Quiet) { printStats(Context.getStats()); - if (DisableFixes) + if (DisableFixes && Behaviour != FB_NoFix) llvm::errs() << "Found compiler errors, but -fix-errors was not specified.\n" "Fixes have NOT been applied.\n\n"; diff --git a/clang-tools-extra/clangd/CMakeLists.txt b/clang-tools-extra/clangd/CMakeLists.txt index bf654a23e00c7da7dc096bb99eab1fceda6bd476..a6229a22900256ee7bc4f5e24814347867234d9d 100644 --- a/clang-tools-extra/clangd/CMakeLists.txt +++ b/clang-tools-extra/clangd/CMakeLists.txt @@ -62,6 +62,7 @@ add_clang_library(clangDaemon DraftStore.cpp DumpAST.cpp ExpectedTypes.cpp + FeatureModule.cpp FindSymbols.cpp FindTarget.cpp FileDistance.cpp @@ -75,7 +76,6 @@ add_clang_library(clangDaemon Hover.cpp IncludeFixer.cpp JSONTransport.cpp - Module.cpp PathMapping.cpp Protocol.cpp Quality.cpp diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp index f2ec8189b92c7477697f5fd7a69643e1baa71a8f..8875f85c8b7066422c149bf02c64075b5d3c4e6f 100644 --- a/clang-tools-extra/clangd/ClangdLSPServer.cpp +++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp @@ -59,8 +59,8 @@ constexpr trace::Metric LSPLatency("lsp_latency", trace::Metric::Distribution, // LSP defines file versions as numbers that increase. // ClangdServer treats them as opaque and therefore uses strings instead. -std::string encodeVersion(int64_t LSPVersion) { - return llvm::to_string(LSPVersion); +std::string encodeVersion(llvm::Optional LSPVersion) { + return LSPVersion ? llvm::to_string(*LSPVersion) : ""; } llvm::Optional decodeVersion(llvm::StringRef Encoded) { int64_t Result; @@ -124,15 +124,15 @@ CompletionItemKindBitset defaultCompletionItemKinds() { // Makes sure edits in \p FE are applicable to latest file contents reported by // editor. If not generates an error message containing information about files // that needs to be saved. -llvm::Error validateEdits(const DraftStore &DraftMgr, const FileEdits &FE) { +llvm::Error validateEdits(const ClangdServer &Server, const FileEdits &FE) { size_t InvalidFileCount = 0; llvm::StringRef LastInvalidFile; for (const auto &It : FE) { - if (auto Draft = DraftMgr.getDraft(It.first())) { + if (auto Draft = Server.getDraft(It.first())) { // If the file is open in user's editor, make sure the version we // saw and current version are compatible as this is the text that // will be replaced by editors. - if (!It.second.canApplyTo(Draft->Contents)) { + if (!It.second.canApplyTo(*Draft)) { ++InvalidFileCount; LastInvalidFile = It.first(); } @@ -579,9 +579,9 @@ void ClangdLSPServer::onInitialize(const InitializeParams &Params, { LSPBinder Binder(Handlers, *this); - bindMethods(Binder); - if (Opts.Modules) - for (auto &Mod : *Opts.Modules) + bindMethods(Binder, Params.capabilities); + if (Opts.FeatureModules) + for (auto &Mod : *Opts.FeatureModules) Mod.initializeLSP(Binder, Params.rawCapabilities, ServerCaps); } @@ -648,8 +648,8 @@ void ClangdLSPServer::onDocumentDidOpen( const std::string &Contents = Params.textDocument.text; - auto Version = DraftMgr.addDraft(File, Params.textDocument.version, Contents); - Server->addDocument(File, Contents, encodeVersion(Version), + Server->addDocument(File, Contents, + encodeVersion(Params.textDocument.version), WantDiagnostics::Yes); } @@ -661,25 +661,29 @@ void ClangdLSPServer::onDocumentDidChange( : WantDiagnostics::No; PathRef File = Params.textDocument.uri.file(); - llvm::Expected Draft = DraftMgr.updateDraft( - File, Params.textDocument.version, Params.contentChanges); - if (!Draft) { - // If this fails, we are most likely going to be not in sync anymore with - // the client. It is better to remove the draft and let further operations - // fail rather than giving wrong results. - DraftMgr.removeDraft(File); - Server->removeDocument(File); - elog("Failed to update {0}: {1}", File, Draft.takeError()); + auto Code = Server->getDraft(File); + if (!Code) { + log("Trying to incrementally change non-added document: {0}", File); return; } - - Server->addDocument(File, Draft->Contents, encodeVersion(Draft->Version), + std::string NewCode(*Code); + for (const auto &Change : Params.contentChanges) { + if (auto Err = applyChange(NewCode, Change)) { + // If this fails, we are most likely going to be not in sync anymore with + // the client. It is better to remove the draft and let further + // operations fail rather than giving wrong results. + Server->removeDocument(File); + elog("Failed to update {0}: {1}", File, std::move(Err)); + return; + } + } + Server->addDocument(File, NewCode, encodeVersion(Params.textDocument.version), WantDiags, Params.forceRebuild); } void ClangdLSPServer::onDocumentDidSave( const DidSaveTextDocumentParams &Params) { - reparseOpenFilesIfNeeded([](llvm::StringRef) { return true; }); + Server->reparseOpenFilesIfNeeded([](llvm::StringRef) { return true; }); } void ClangdLSPServer::onFileEvent(const DidChangeWatchedFilesParams &Params) { @@ -720,13 +724,8 @@ void ClangdLSPServer::onCommandApplyEdit(const WorkspaceEdit &WE, void ClangdLSPServer::onCommandApplyTweak(const TweakArgs &Args, Callback Reply) { - auto Code = DraftMgr.getDraft(Args.file.file()); - if (!Code) - return Reply(error("trying to apply a code action for a non-added file")); - - auto Action = [this, Reply = std::move(Reply), File = Args.file, - Code = std::move(*Code)]( - llvm::Expected R) mutable { + auto Action = [this, Reply = std::move(Reply), + File = Args.file](llvm::Expected R) mutable { if (!R) return Reply(R.takeError()); @@ -742,7 +741,7 @@ void ClangdLSPServer::onCommandApplyTweak(const TweakArgs &Args, if (R->ApplyEdits.empty()) return Reply("Tweak applied."); - if (auto Err = validateEdits(DraftMgr, R->ApplyEdits)) + if (auto Err = validateEdits(*Server, R->ApplyEdits)) return Reply(std::move(Err)); WorkspaceEdit WE; @@ -808,7 +807,7 @@ void ClangdLSPServer::onPrepareRename(const TextDocumentPositionParams &Params, void ClangdLSPServer::onRename(const RenameParams &Params, Callback Reply) { Path File = std::string(Params.textDocument.uri.file()); - if (!DraftMgr.getDraft(File)) + if (!Server->getDraft(File)) return Reply(llvm::make_error( "onRename called for non-added file", ErrorCode::InvalidParams)); Server->rename( @@ -817,7 +816,7 @@ void ClangdLSPServer::onRename(const RenameParams &Params, this](llvm::Expected R) mutable { if (!R) return Reply(R.takeError()); - if (auto Err = validateEdits(DraftMgr, R->GlobalChanges)) + if (auto Err = validateEdits(*Server, R->GlobalChanges)) return Reply(std::move(Err)); WorkspaceEdit Result; Result.changes.emplace(); @@ -832,7 +831,6 @@ void ClangdLSPServer::onRename(const RenameParams &Params, void ClangdLSPServer::onDocumentDidClose( const DidCloseTextDocumentParams &Params) { PathRef File = Params.textDocument.uri.file(); - DraftMgr.removeDraft(File); Server->removeDocument(File); { @@ -857,52 +855,35 @@ void ClangdLSPServer::onDocumentOnTypeFormatting( const DocumentOnTypeFormattingParams &Params, Callback> Reply) { auto File = Params.textDocument.uri.file(); - auto Code = DraftMgr.getDraft(File); - if (!Code) - return Reply(llvm::make_error( - "onDocumentOnTypeFormatting called for non-added file", - ErrorCode::InvalidParams)); - - Server->formatOnType(File, Code->Contents, Params.position, Params.ch, - std::move(Reply)); + Server->formatOnType(File, Params.position, Params.ch, std::move(Reply)); } void ClangdLSPServer::onDocumentRangeFormatting( const DocumentRangeFormattingParams &Params, Callback> Reply) { auto File = Params.textDocument.uri.file(); - auto Code = DraftMgr.getDraft(File); - if (!Code) - return Reply(llvm::make_error( - "onDocumentRangeFormatting called for non-added file", - ErrorCode::InvalidParams)); - - Server->formatRange( - File, Code->Contents, Params.range, - [Code = Code->Contents, Reply = std::move(Reply)]( - llvm::Expected Result) mutable { - if (Result) - Reply(replacementsToEdits(Code, Result.get())); - else - Reply(Result.takeError()); - }); + auto Code = Server->getDraft(File); + Server->formatFile(File, Params.range, + [Code = std::move(Code), Reply = std::move(Reply)]( + llvm::Expected Result) mutable { + if (Result) + Reply(replacementsToEdits(*Code, Result.get())); + else + Reply(Result.takeError()); + }); } void ClangdLSPServer::onDocumentFormatting( const DocumentFormattingParams &Params, Callback> Reply) { auto File = Params.textDocument.uri.file(); - auto Code = DraftMgr.getDraft(File); - if (!Code) - return Reply(llvm::make_error( - "onDocumentFormatting called for non-added file", - ErrorCode::InvalidParams)); - - Server->formatFile(File, Code->Contents, - [Code = Code->Contents, Reply = std::move(Reply)]( + auto Code = Server->getDraft(File); + Server->formatFile(File, + /*Rng=*/llvm::None, + [Code = std::move(Code), Reply = std::move(Reply)]( llvm::Expected Result) mutable { if (Result) - Reply(replacementsToEdits(Code, Result.get())); + Reply(replacementsToEdits(*Code, Result.get())); else Reply(Result.takeError()); }); @@ -978,11 +959,6 @@ static llvm::Optional asCommand(const CodeAction &Action) { void ClangdLSPServer::onCodeAction(const CodeActionParams &Params, Callback Reply) { URIForFile File = Params.textDocument.uri; - auto Code = DraftMgr.getDraft(File.file()); - if (!Code) - return Reply(llvm::make_error( - "onCodeAction called for non-added file", ErrorCode::InvalidParams)); - // Checks whether a particular CodeActionKind is included in the response. auto KindAllowed = [Only(Params.context.only)](llvm::StringRef Kind) { if (Only.empty()) @@ -1005,8 +981,8 @@ void ClangdLSPServer::onCodeAction(const CodeActionParams &Params, // Now enumerate the semantic code actions. auto ConsumeActions = - [Reply = std::move(Reply), File, Code = std::move(*Code), - Selection = Params.range, FixIts = std::move(FixIts), this]( + [Reply = std::move(Reply), File, Selection = Params.range, + FixIts = std::move(FixIts), this]( llvm::Expected> Tweaks) mutable { if (!Tweaks) return Reply(Tweaks.takeError()); @@ -1246,7 +1222,7 @@ void ClangdLSPServer::applyConfiguration( } } - reparseOpenFilesIfNeeded( + Server->reparseOpenFilesIfNeeded( [&](llvm::StringRef File) { return ModifiedFiles.count(File) != 0; }); } @@ -1431,8 +1407,7 @@ void ClangdLSPServer::onAST(const ASTParams &Params, Server->getAST(Params.textDocument.uri.file(), Params.range, std::move(CB)); } -ClangdLSPServer::ClangdLSPServer(Transport &Transp, - const ThreadsafeFS &TFS, +ClangdLSPServer::ClangdLSPServer(Transport &Transp, const ThreadsafeFS &TFS, const ClangdLSPServer::Options &Opts) : ShouldProfile(/*Period=*/std::chrono::minutes(5), /*Delay=*/std::chrono::minutes(1)), @@ -1452,7 +1427,8 @@ ClangdLSPServer::ClangdLSPServer(Transport &Transp, Bind.method("initialize", this, &ClangdLSPServer::onInitialize); } -void ClangdLSPServer::bindMethods(LSPBinder &Bind) { +void ClangdLSPServer::bindMethods(LSPBinder &Bind, + const ClientCapabilities &Caps) { // clang-format off Bind.notification("initialized", this, &ClangdLSPServer::onInitialized); Bind.method("shutdown", this, &ClangdLSPServer::onShutdown); @@ -1506,6 +1482,8 @@ void ClangdLSPServer::bindMethods(LSPBinder &Bind) { BeginWorkDoneProgress = Bind.outgoingNotification("$/progress"); ReportWorkDoneProgress = Bind.outgoingNotification("$/progress"); EndWorkDoneProgress = Bind.outgoingNotification("$/progress"); + if(Caps.SemanticTokenRefreshSupport) + SemanticTokensRefresh = Bind.outgoingMethod("workspace/semanticTokens/refresh"); // clang-format on } @@ -1557,17 +1535,17 @@ bool ClangdLSPServer::shouldRunCompletion( const CompletionParams &Params) const { if (Params.context.triggerKind != CompletionTriggerKind::TriggerCharacter) return true; - auto Code = DraftMgr.getDraft(Params.textDocument.uri.file()); + auto Code = Server->getDraft(Params.textDocument.uri.file()); if (!Code) return true; // completion code will log the error for untracked doc. - auto Offset = positionToOffset(Code->Contents, Params.position, + auto Offset = positionToOffset(*Code, Params.position, /*AllowColumnsBeyondLineLength=*/false); if (!Offset) { vlog("could not convert position '{0}' to offset for file '{1}'", Params.position, Params.textDocument.uri.file()); return true; } - return allowImplicitCompletion(Code->Contents, *Offset); + return allowImplicitCompletion(*Code, *Offset); } void ClangdLSPServer::onDiagnosticsReady(PathRef File, llvm::StringRef Version, @@ -1681,16 +1659,14 @@ void ClangdLSPServer::onFileUpdated(PathRef File, const TUStatus &Status) { NotifyFileStatus(Status.render(File)); } -void ClangdLSPServer::reparseOpenFilesIfNeeded( - llvm::function_ref Filter) { - // Reparse only opened files that were modified. - for (const Path &FilePath : DraftMgr.getActiveFiles()) - if (Filter(FilePath)) - if (auto Draft = DraftMgr.getDraft(FilePath)) // else disappeared in race? - Server->addDocument(FilePath, std::move(Draft->Contents), - encodeVersion(Draft->Version), - WantDiagnostics::Auto); +void ClangdLSPServer::onSemanticsMaybeChanged(PathRef File) { + if (SemanticTokensRefresh) { + SemanticTokensRefresh(NoParams{}, [](llvm::Expected E) { + if (E) + return; + elog("Failed to refresh semantic tokens: {0}", E.takeError()); + }); + } } - } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/ClangdLSPServer.h b/clang-tools-extra/clangd/ClangdLSPServer.h index eeabebbc23cb21e4e7b682cacb67defddf15227a..01d0ec20f098e4ca81f18295b8fbc4bab9a4bf97 100644 --- a/clang-tools-extra/clangd/ClangdLSPServer.h +++ b/clang-tools-extra/clangd/ClangdLSPServer.h @@ -87,6 +87,7 @@ private: std::vector Diagnostics) override; void onFileUpdated(PathRef File, const TUStatus &Status) override; void onBackgroundIndexProgress(const BackgroundQueue::Stats &Stats) override; + void onSemanticsMaybeChanged(PathRef File) override; // LSP methods. Notifications have signature void(const Params&). // Calls have signature void(const Params&, Callback). @@ -181,11 +182,12 @@ private: ReportWorkDoneProgress; LSPBinder::OutgoingNotification> EndWorkDoneProgress; + LSPBinder::OutgoingMethod SemanticTokensRefresh; void applyEdit(WorkspaceEdit WE, llvm::json::Value Success, Callback Reply); - void bindMethods(LSPBinder &); + void bindMethods(LSPBinder &, const ClientCapabilities &Caps); std::vector getFixes(StringRef File, const clangd::Diagnostic &D); /// Checks if completion request should be ignored. We need this due to the @@ -194,12 +196,6 @@ private: /// produce '->' and '::', respectively. bool shouldRunCompletion(const CompletionParams &Params) const; - /// Requests a reparse of currently opened files using their latest source. - /// This will typically only rebuild if something other than the source has - /// changed (e.g. the CDB yields different flags, or files included in the - /// preamble have been modified). - void reparseOpenFilesIfNeeded( - llvm::function_ref Filter); void applyConfiguration(const ConfigurationSettings &Settings); /// Runs profiling and exports memory usage metrics if tracing is enabled and @@ -282,8 +278,6 @@ private: BackgroundQueue::Stats PendingBackgroundIndexProgress; /// LSP extension: skip WorkDoneProgressCreate, just send progress streams. bool BackgroundIndexSkipCreate = false; - // Store of the current versions of the open documents. - DraftStore DraftMgr; Options Opts; // The CDB is created by the "initialize" LSP method. diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp index 245a2d081f93cf52a2a667b2d57ada2a078aeed9..df3dd363800a08a017114fdc1047e96ff11bed73 100644 --- a/clang-tools-extra/clangd/ClangdServer.cpp +++ b/clang-tools-extra/clangd/ClangdServer.cpp @@ -73,6 +73,8 @@ struct UpdateIndexCallbacks : public ParsingCallbacks { const CanonicalIncludes &CanonIncludes) override { if (FIndex) FIndex->updatePreamble(Path, Version, Ctx, std::move(PP), CanonIncludes); + if (ServerCallbacks) + ServerCallbacks->onSemanticsMaybeChanged(Path); } void onMainAST(PathRef Path, ParsedAST &AST, PublishFn Publish) override { @@ -104,6 +106,23 @@ private: ClangdServer::Callbacks *ServerCallbacks; }; +class DraftStoreFS : public ThreadsafeFS { +public: + DraftStoreFS(const ThreadsafeFS &Base, const DraftStore &Drafts) + : Base(Base), DirtyFiles(Drafts) {} + +private: + llvm::IntrusiveRefCntPtr viewImpl() const override { + auto OFS = llvm::makeIntrusiveRefCnt( + Base.view(llvm::None)); + OFS->pushOverlay(DirtyFiles.asVFS()); + return OFS; + } + + const ThreadsafeFS &Base; + const DraftStore &DirtyFiles; +}; + } // namespace ClangdServer::Options ClangdServer::optsForTest() { @@ -127,10 +146,11 @@ ClangdServer::Options::operator TUScheduler::Options() const { ClangdServer::ClangdServer(const GlobalCompilationDatabase &CDB, const ThreadsafeFS &TFS, const Options &Opts, Callbacks *Callbacks) - : Modules(Opts.Modules), CDB(CDB), TFS(TFS), + : FeatureModules(Opts.FeatureModules), CDB(CDB), TFS(TFS), DynamicIdx(Opts.BuildDynamicSymbolIndex ? new FileIndex() : nullptr), ClangTidyProvider(Opts.ClangTidyProvider), - WorkspaceRoot(Opts.WorkspaceRoot) { + WorkspaceRoot(Opts.WorkspaceRoot), + DirtyFS(std::make_unique(TFS, DraftMgr)) { // Pass a callback into `WorkScheduler` to extract symbols from a newly // parsed file and rebuild the file index synchronously each time an AST // is parsed. @@ -166,13 +186,13 @@ ClangdServer::ClangdServer(const GlobalCompilationDatabase &CDB, if (DynamicIdx) AddIndex(DynamicIdx.get()); - if (Opts.Modules) { - Module::Facilities F{ + if (Opts.FeatureModules) { + FeatureModule::Facilities F{ *this->WorkScheduler, this->Index, this->TFS, }; - for (auto &Mod : *Opts.Modules) + for (auto &Mod : *Opts.FeatureModules) Mod.initialize(F); } } @@ -182,11 +202,11 @@ ClangdServer::~ClangdServer() { // otherwise access members concurrently. // (Nobody can be using TUScheduler because we're on the main thread). WorkScheduler.reset(); - // Now requests have stopped, we can shut down modules. - if (Modules) { - for (auto &Mod : *Modules) + // Now requests have stopped, we can shut down feature modules. + if (FeatureModules) { + for (auto &Mod : *FeatureModules) Mod.stop(); - for (auto &Mod : *Modules) + for (auto &Mod : *FeatureModules) Mod.blockUntilIdle(Deadline::infinity()); } } @@ -194,13 +214,14 @@ ClangdServer::~ClangdServer() { void ClangdServer::addDocument(PathRef File, llvm::StringRef Contents, llvm::StringRef Version, WantDiagnostics WantDiags, bool ForceRebuild) { + std::string ActualVersion = DraftMgr.addDraft(File, Version, Contents); ParseOptions Opts; // Compile command is set asynchronously during update, as it can be slow. ParseInputs Inputs; Inputs.TFS = &TFS; Inputs.Contents = std::string(Contents); - Inputs.Version = Version.str(); + Inputs.Version = std::move(ActualVersion); Inputs.ForceRebuild = ForceRebuild; Inputs.Opts = std::move(Opts); Inputs.Index = Index; @@ -211,6 +232,23 @@ void ClangdServer::addDocument(PathRef File, llvm::StringRef Contents, BackgroundIdx->boostRelated(File); } +void ClangdServer::reparseOpenFilesIfNeeded( + llvm::function_ref Filter) { + // Reparse only opened files that were modified. + for (const Path &FilePath : DraftMgr.getActiveFiles()) + if (Filter(FilePath)) + if (auto Draft = DraftMgr.getDraft(FilePath)) // else disappeared in race? + addDocument(FilePath, *Draft->Contents, Draft->Version, + WantDiagnostics::Auto); +} + +std::shared_ptr ClangdServer::getDraft(PathRef File) const { + auto Draft = DraftMgr.getDraft(File); + if (!Draft) + return nullptr; + return std::move(Draft->Contents); +} + std::function ClangdServer::createConfiguredContextProvider(const config::Provider *Provider, Callbacks *Publish) { @@ -288,7 +326,10 @@ ClangdServer::createConfiguredContextProvider(const config::Provider *Provider, }; } -void ClangdServer::removeDocument(PathRef File) { WorkScheduler->remove(File); } +void ClangdServer::removeDocument(PathRef File) { + DraftMgr.removeDraft(File); + WorkScheduler->remove(File); +} void ClangdServer::codeComplete(PathRef File, Position Pos, const clangd::CodeCompleteOptions &Opts, @@ -372,31 +413,55 @@ void ClangdServer::signatureHelp(PathRef File, Position Pos, std::move(Action)); } -void ClangdServer::formatRange(PathRef File, llvm::StringRef Code, Range Rng, - Callback CB) { - llvm::Expected Begin = positionToOffset(Code, Rng.start); - if (!Begin) - return CB(Begin.takeError()); - llvm::Expected End = positionToOffset(Code, Rng.end); - if (!End) - return CB(End.takeError()); - formatCode(File, Code, {tooling::Range(*Begin, *End - *Begin)}, - std::move(CB)); -} - -void ClangdServer::formatFile(PathRef File, llvm::StringRef Code, +void ClangdServer::formatFile(PathRef File, llvm::Optional Rng, Callback CB) { - // Format everything. - formatCode(File, Code, {tooling::Range(0, Code.size())}, std::move(CB)); + auto Code = getDraft(File); + if (!Code) + return CB(llvm::make_error("trying to format non-added document", + ErrorCode::InvalidParams)); + tooling::Range RequestedRange; + if (Rng) { + llvm::Expected Begin = positionToOffset(*Code, Rng->start); + if (!Begin) + return CB(Begin.takeError()); + llvm::Expected End = positionToOffset(*Code, Rng->end); + if (!End) + return CB(End.takeError()); + RequestedRange = tooling::Range(*Begin, *End - *Begin); + } else { + RequestedRange = tooling::Range(0, Code->size()); + } + + // Call clang-format. + auto Action = [File = File.str(), Code = std::move(*Code), + Ranges = std::vector{RequestedRange}, + CB = std::move(CB), this]() mutable { + format::FormatStyle Style = getFormatStyleForFile(File, Code, TFS); + tooling::Replacements IncludeReplaces = + format::sortIncludes(Style, Code, Ranges, File); + auto Changed = tooling::applyAllReplacements(Code, IncludeReplaces); + if (!Changed) + return CB(Changed.takeError()); + + CB(IncludeReplaces.merge(format::reformat( + Style, *Changed, + tooling::calculateRangesAfterReplacements(IncludeReplaces, Ranges), + File))); + }; + WorkScheduler->runQuick("Format", File, std::move(Action)); } -void ClangdServer::formatOnType(PathRef File, llvm::StringRef Code, - Position Pos, StringRef TriggerText, +void ClangdServer::formatOnType(PathRef File, Position Pos, + StringRef TriggerText, Callback> CB) { - llvm::Expected CursorPos = positionToOffset(Code, Pos); + auto Code = getDraft(File); + if (!Code) + return CB(llvm::make_error("trying to format non-added document", + ErrorCode::InvalidParams)); + llvm::Expected CursorPos = positionToOffset(*Code, Pos); if (!CursorPos) return CB(CursorPos.takeError()); - auto Action = [File = File.str(), Code = Code.str(), + auto Action = [File = File.str(), Code = std::move(*Code), TriggerText = TriggerText.str(), CursorPos = *CursorPos, CB = std::move(CB), this]() mutable { auto Style = format::getStyle(format::DefaultFormatStyle, File, @@ -425,9 +490,10 @@ void ClangdServer::prepareRename(PathRef File, Position Pos, return CB(InpAST.takeError()); // prepareRename is latency-sensitive: we don't query the index, as we // only need main-file references - auto Results = clangd::rename( - {Pos, NewName.getValueOr("__clangd_rename_dummy"), InpAST->AST, File, - /*Index=*/nullptr, RenameOpts}); + auto Results = + clangd::rename({Pos, NewName.getValueOr("__clangd_rename_dummy"), + InpAST->AST, File, /*FS=*/nullptr, + /*Index=*/nullptr, RenameOpts}); if (!Results) { // LSP says to return null on failure, but that will result in a generic // failure message. If we send an LSP error response, clients can surface @@ -442,25 +508,16 @@ void ClangdServer::prepareRename(PathRef File, Position Pos, void ClangdServer::rename(PathRef File, Position Pos, llvm::StringRef NewName, const RenameOptions &Opts, Callback CB) { - // A snapshot of all file dirty buffers. - llvm::StringMap Snapshot = WorkScheduler->getAllFileContents(); auto Action = [File = File.str(), NewName = NewName.str(), Pos, Opts, - CB = std::move(CB), Snapshot = std::move(Snapshot), + CB = std::move(CB), this](llvm::Expected InpAST) mutable { // Tracks number of files edited per invocation. static constexpr trace::Metric RenameFiles("rename_files", trace::Metric::Distribution); if (!InpAST) return CB(InpAST.takeError()); - auto GetDirtyBuffer = - [&Snapshot](PathRef AbsPath) -> llvm::Optional { - auto It = Snapshot.find(AbsPath); - if (It == Snapshot.end()) - return llvm::None; - return It->second; - }; - auto R = clangd::rename( - {Pos, NewName, InpAST->AST, File, Index, Opts, GetDirtyBuffer}); + auto R = clangd::rename({Pos, NewName, InpAST->AST, File, + DirtyFS->view(llvm::None), Index, Opts}); if (!R) return CB(R.takeError()); @@ -616,27 +673,6 @@ void ClangdServer::switchSourceHeader( WorkScheduler->runWithAST("SwitchHeaderSource", Path, std::move(Action)); } -void ClangdServer::formatCode(PathRef File, llvm::StringRef Code, - llvm::ArrayRef Ranges, - Callback CB) { - // Call clang-format. - auto Action = [File = File.str(), Code = Code.str(), Ranges = Ranges.vec(), - CB = std::move(CB), this]() mutable { - format::FormatStyle Style = getFormatStyleForFile(File, Code, TFS); - tooling::Replacements IncludeReplaces = - format::sortIncludes(Style, Code, Ranges, File); - auto Changed = tooling::applyAllReplacements(Code, IncludeReplaces); - if (!Changed) - return CB(Changed.takeError()); - - CB(IncludeReplaces.merge(format::reformat( - Style, *Changed, - tooling::calculateRangesAfterReplacements(IncludeReplaces, Ranges), - File))); - }; - WorkScheduler->runQuick("Format", File, std::move(Action)); -} - void ClangdServer::findDocumentHighlights( PathRef File, Position Pos, Callback> CB) { auto Action = @@ -894,7 +930,7 @@ ClangdServer::blockUntilIdleForTest(llvm::Optional TimeoutSeconds) { return false; if (BackgroundIdx && !BackgroundIdx->blockUntilIdleForTest(Timeout)) return false; - if (Modules && llvm::any_of(*Modules, [&](Module &M) { + if (FeatureModules && llvm::any_of(*FeatureModules, [&](FeatureModule &M) { return !M.blockUntilIdle(timeoutSeconds(Timeout)); })) return false; diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h index 198395c4a8f388781106458402e1ec7ff28e10da..e76ef65922ee076afbf0623d9bbb90359eeb06af 100644 --- a/clang-tools-extra/clangd/ClangdServer.h +++ b/clang-tools-extra/clangd/ClangdServer.h @@ -12,9 +12,10 @@ #include "../clang-tidy/ClangTidyOptions.h" #include "CodeComplete.h" #include "ConfigProvider.h" +#include "DraftStore.h" +#include "FeatureModule.h" #include "GlobalCompilationDatabase.h" #include "Hover.h" -#include "Module.h" #include "Protocol.h" #include "SemanticHighlighting.h" #include "TUScheduler.h" @@ -27,6 +28,7 @@ #include "support/Cancellation.h" #include "support/Function.h" #include "support/MemoryTree.h" +#include "support/Path.h" #include "support/ThreadsafeFS.h" #include "clang/Tooling/CompilationDatabase.h" #include "clang/Tooling/Core/Replacement.h" @@ -73,6 +75,14 @@ public: /// Not called concurrently. virtual void onBackgroundIndexProgress(const BackgroundQueue::Stats &Stats) {} + + /// Called when the meaning of a source code may have changed without an + /// edit. Usually clients assume that responses to requests are valid until + /// they next edit the file. If they're invalidated at other times, we + /// should tell the client. In particular, when an asynchronous preamble + /// build finishes, we can provide more accurate semantic tokens, so we + /// should tell the client to refresh. + virtual void onSemanticsMaybeChanged(PathRef File) {} }; /// Creates a context provider that loads and installs config. /// Errors in loading config are reported as diagnostics via Callbacks. @@ -143,7 +153,7 @@ public: /// Enable preview of FoldingRanges feature. bool FoldingRanges = false; - ModuleSet *Modules = nullptr; + FeatureModuleSet *FeatureModules = nullptr; explicit operator TUScheduler::Options() const; }; @@ -162,13 +172,13 @@ public: const Options &Opts, Callbacks *Callbacks = nullptr); ~ClangdServer(); - /// Gets the installed module of a given type, if any. - /// This exposes access the public interface of modules that have one. - template Mod *getModule() { - return Modules ? Modules->get() : nullptr; + /// Gets the installed feature module of a given type, if any. + /// This exposes access the public interface of feature modules that have one. + template Mod *featureModule() { + return FeatureModules ? FeatureModules->get() : nullptr; } - template const Mod *getModule() const { - return Modules ? Modules->get() : nullptr; + template const Mod *featureModule() const { + return FeatureModules ? FeatureModules->get() : nullptr; } /// Add a \p File to the list of tracked C++ files or update the contents if @@ -176,7 +186,7 @@ public: /// separate thread. When the parsing is complete, DiagConsumer passed in /// constructor will receive onDiagnosticsReady callback. /// Version identifies this snapshot and is propagated to ASTs, preambles, - /// diagnostics etc built from it. + /// diagnostics etc built from it. If empty, a version number is generated. void addDocument(PathRef File, StringRef Contents, llvm::StringRef Version = "null", WantDiagnostics WD = WantDiagnostics::Auto, @@ -188,6 +198,13 @@ public: /// An empty set of diagnostics will be delivered, with Version = "". void removeDocument(PathRef File); + /// Requests a reparse of currently opened files using their latest source. + /// This will typically only rebuild if something other than the source has + /// changed (e.g. the CDB yields different flags, or files included in the + /// preamble have been modified). + void reparseOpenFilesIfNeeded( + llvm::function_ref Filter); + /// Run code completion for \p File at \p Pos. /// /// This method should only be called for currently tracked files. @@ -253,18 +270,15 @@ public: void findReferences(PathRef File, Position Pos, uint32_t Limit, Callback CB); - /// Run formatting for \p Rng inside \p File with content \p Code. - void formatRange(PathRef File, StringRef Code, Range Rng, - Callback CB); - - /// Run formatting for the whole \p File with content \p Code. - void formatFile(PathRef File, StringRef Code, + /// Run formatting for the \p File with content \p Code. + /// If \p Rng is non-null, formats only that region. + void formatFile(PathRef File, llvm::Optional Rng, Callback CB); /// Run formatting after \p TriggerText was typed at \p Pos in \p File with /// content \p Code. - void formatOnType(PathRef File, StringRef Code, Position Pos, - StringRef TriggerText, Callback> CB); + void formatOnType(PathRef File, Position Pos, StringRef TriggerText, + Callback> CB); /// Test the validity of a rename operation. /// @@ -334,6 +348,10 @@ public: /// FIXME: those metrics might be useful too, we should add them. llvm::StringMap fileStats() const; + /// Gets the contents of a currently tracked file. Returns nullptr if the file + /// isn't being tracked. + std::shared_ptr getDraft(PathRef File) const; + // Blocks the main thread until the server is idle. Only for use in tests. // Returns false if the timeout expires. // FIXME: various subcomponents each get the full timeout, so it's more of @@ -345,11 +363,7 @@ public: void profile(MemoryTree &MT) const; private: - void formatCode(PathRef File, llvm::StringRef Code, - ArrayRef Ranges, - Callback CB); - - ModuleSet *Modules; + FeatureModuleSet *FeatureModules; const GlobalCompilationDatabase &CDB; const ThreadsafeFS &TFS; @@ -377,6 +391,12 @@ private: llvm::Optional WorkspaceRoot; llvm::Optional WorkScheduler; + + // Store of the current versions of the open documents. + // Only written from the main thread (despite being threadsafe). + DraftStore DraftMgr; + + std::unique_ptr DirtyFS; }; } // namespace clangd diff --git a/clang-tools-extra/clangd/CodeComplete.h b/clang-tools-extra/clangd/CodeComplete.h index debf71d4117c2d083197f4252ab3895d8100994d..40a528caa9393b54a35a150e0e5095de647d015c 100644 --- a/clang-tools-extra/clangd/CodeComplete.h +++ b/clang-tools-extra/clangd/CodeComplete.h @@ -133,7 +133,7 @@ struct CodeCompleteOptions { enum CodeCompletionRankingModel { Heuristics, DecisionForest, - } RankingModel = Heuristics; + } RankingModel = DecisionForest; /// Callback used to score a CompletionCandidate if DecisionForest ranking /// model is enabled. diff --git a/clang-tools-extra/clangd/DraftStore.cpp b/clang-tools-extra/clangd/DraftStore.cpp index 1299efbfba9fa4ca05a491ec787842510b3a9915..e040d1ee93d66f0e47158ac904c5d5b082961eb0 100644 --- a/clang-tools-extra/clangd/DraftStore.cpp +++ b/clang-tools-extra/clangd/DraftStore.cpp @@ -9,7 +9,10 @@ #include "DraftStore.h" #include "SourceCode.h" #include "support/Logger.h" +#include "llvm/ADT/StringExtras.h" #include "llvm/Support/Errc.h" +#include "llvm/Support/VirtualFileSystem.h" +#include namespace clang { namespace clangd { @@ -21,7 +24,7 @@ llvm::Optional DraftStore::getDraft(PathRef File) const { if (It == Drafts.end()) return None; - return It->second; + return It->second.D; } std::vector DraftStore::getActiveFiles() const { @@ -34,102 +37,92 @@ std::vector DraftStore::getActiveFiles() const { return ResultVector; } +static void increment(std::string &S) { + // Ensure there is a numeric suffix. + if (S.empty() || !llvm::isDigit(S.back())) { + S.push_back('0'); + return; + } + // Increment the numeric suffix. + auto I = S.rbegin(), E = S.rend(); + for (;;) { + if (I == E || !llvm::isDigit(*I)) { + // Reached start of numeric section, it was all 9s. + S.insert(I.base(), '1'); + break; + } + if (*I != '9') { + // Found a digit we can increment, we're done. + ++*I; + break; + } + *I = '0'; // and keep incrementing to the left. + } +} + static void updateVersion(DraftStore::Draft &D, - llvm::Optional Version) { - if (Version) { + llvm::StringRef SpecifiedVersion) { + if (!SpecifiedVersion.empty()) { // We treat versions as opaque, but the protocol says they increase. - if (*Version <= D.Version) - log("File version went from {0} to {1}", D.Version, Version); - D.Version = *Version; + if (SpecifiedVersion.compare_numeric(D.Version) <= 0) + log("File version went from {0} to {1}", D.Version, SpecifiedVersion); + D.Version = SpecifiedVersion.str(); } else { - // Note that if D was newly-created, this will bump D.Version from -1 to 0. - ++D.Version; + // Note that if D was newly-created, this will bump D.Version from "" to 1. + increment(D.Version); } } -int64_t DraftStore::addDraft(PathRef File, llvm::Optional Version, - llvm::StringRef Contents) { +std::string DraftStore::addDraft(PathRef File, llvm::StringRef Version, + llvm::StringRef Contents) { std::lock_guard Lock(Mutex); - Draft &D = Drafts[File]; - updateVersion(D, Version); - D.Contents = Contents.str(); - return D.Version; + auto &D = Drafts[File]; + updateVersion(D.D, Version); + std::time(&D.MTime); + D.D.Contents = std::make_shared(Contents); + return D.D.Version; } -llvm::Expected DraftStore::updateDraft( - PathRef File, llvm::Optional Version, - llvm::ArrayRef Changes) { +void DraftStore::removeDraft(PathRef File) { std::lock_guard Lock(Mutex); - auto EntryIt = Drafts.find(File); - if (EntryIt == Drafts.end()) { - return error(llvm::errc::invalid_argument, - "Trying to do incremental update on non-added document: {0}", - File); - } - Draft &D = EntryIt->second; - std::string Contents = EntryIt->second.Contents; + Drafts.erase(File); +} - for (const TextDocumentContentChangeEvent &Change : Changes) { - if (!Change.range) { - Contents = Change.text; - continue; - } +namespace { + +/// A read only MemoryBuffer shares ownership of a ref counted string. The +/// shared string object must not be modified while an owned by this buffer. +class SharedStringBuffer : public llvm::MemoryBuffer { + const std::shared_ptr BufferContents; + const std::string Name; - const Position &Start = Change.range->start; - llvm::Expected StartIndex = - positionToOffset(Contents, Start, false); - if (!StartIndex) - return StartIndex.takeError(); - - const Position &End = Change.range->end; - llvm::Expected EndIndex = positionToOffset(Contents, End, false); - if (!EndIndex) - return EndIndex.takeError(); - - if (*EndIndex < *StartIndex) - return error(llvm::errc::invalid_argument, - "Range's end position ({0}) is before start position ({1})", - End, Start); - - // Since the range length between two LSP positions is dependent on the - // contents of the buffer we compute the range length between the start and - // end position ourselves and compare it to the range length of the LSP - // message to verify the buffers of the client and server are in sync. - - // EndIndex and StartIndex are in bytes, but Change.rangeLength is in UTF-16 - // code units. - ssize_t ComputedRangeLength = - lspLength(Contents.substr(*StartIndex, *EndIndex - *StartIndex)); - - if (Change.rangeLength && ComputedRangeLength != *Change.rangeLength) - return error(llvm::errc::invalid_argument, - "Change's rangeLength ({0}) doesn't match the " - "computed range length ({1}).", - *Change.rangeLength, ComputedRangeLength); - - std::string NewContents; - NewContents.reserve(*StartIndex + Change.text.length() + - (Contents.length() - *EndIndex)); - - NewContents = Contents.substr(0, *StartIndex); - NewContents += Change.text; - NewContents += Contents.substr(*EndIndex); - - Contents = std::move(NewContents); +public: + BufferKind getBufferKind() const override { + return MemoryBuffer::MemoryBuffer_Malloc; } - updateVersion(D, Version); - D.Contents = std::move(Contents); - return D; -} + StringRef getBufferIdentifier() const override { return Name; } -void DraftStore::removeDraft(PathRef File) { - std::lock_guard Lock(Mutex); - - Drafts.erase(File); + SharedStringBuffer(std::shared_ptr Data, StringRef Name) + : BufferContents(std::move(Data)), Name(Name) { + assert(BufferContents && "Can't create from empty shared_ptr"); + MemoryBuffer::init(BufferContents->c_str(), + BufferContents->c_str() + BufferContents->size(), + /*RequiresNullTerminator=*/true); + } +}; +} // namespace + +llvm::IntrusiveRefCntPtr DraftStore::asVFS() const { + auto MemFS = llvm::makeIntrusiveRefCnt(); + std::lock_guard Guard(Mutex); + for (const auto &Draft : Drafts) + MemFS->addFile(Draft.getKey(), Draft.getValue().MTime, + std::make_unique( + Draft.getValue().D.Contents, Draft.getKey())); + return MemFS; } - } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/DraftStore.h b/clang-tools-extra/clangd/DraftStore.h index 3c2d0c6a4b0fa65e2a7b78760126cbfc69aca073..6b50b23995a0072643fdc7acd45717e9ab6675b6 100644 --- a/clang-tools-extra/clangd/DraftStore.h +++ b/clang-tools-extra/clangd/DraftStore.h @@ -13,6 +13,7 @@ #include "support/Path.h" #include "clang/Basic/LLVM.h" #include "llvm/ADT/StringMap.h" +#include "llvm/Support/VirtualFileSystem.h" #include #include #include @@ -21,15 +22,14 @@ namespace clang { namespace clangd { /// A thread-safe container for files opened in a workspace, addressed by -/// filenames. The contents are owned by the DraftStore. This class supports -/// both whole and incremental updates of the documents. -/// Each time a draft is updated, it is assigned a version number. This can be +/// filenames. The contents are owned by the DraftStore. +/// Each time a draft is updated, it is assigned a version. This can be /// specified by the caller or incremented from the previous version. class DraftStore { public: struct Draft { - std::string Contents; - int64_t Version = -1; + std::shared_ptr Contents; + std::string Version; }; /// \return Contents of the stored document. @@ -40,28 +40,23 @@ public: std::vector getActiveFiles() const; /// Replace contents of the draft for \p File with \p Contents. - /// If no version is specified, one will be automatically assigned. + /// If version is empty, one will be automatically assigned. /// Returns the version. - int64_t addDraft(PathRef File, llvm::Optional Version, - StringRef Contents); - - /// Update the contents of the draft for \p File based on \p Changes. - /// If a position in \p Changes is invalid (e.g. out-of-range), the - /// draft is not modified. - /// If no version is specified, one will be automatically assigned. - /// - /// \return The new version of the draft for \p File, or an error if the - /// changes couldn't be applied. - llvm::Expected - updateDraft(PathRef File, llvm::Optional Version, - llvm::ArrayRef Changes); + std::string addDraft(PathRef File, llvm::StringRef Version, + StringRef Contents); /// Remove the draft from the store. void removeDraft(PathRef File); + llvm::IntrusiveRefCntPtr asVFS() const; + private: + struct DraftAndTime { + Draft D; + std::time_t MTime; + }; mutable std::mutex Mutex; - llvm::StringMap Drafts; + llvm::StringMap Drafts; }; } // namespace clangd diff --git a/clang-tools-extra/clangd/Module.cpp b/clang-tools-extra/clangd/FeatureModule.cpp similarity index 65% rename from clang-tools-extra/clangd/Module.cpp rename to clang-tools-extra/clangd/FeatureModule.cpp index 051f1b45c837597a019df19028183792ca6173c7..85977aadd6e3207eb5dfcfd2630c50b78c3721e8 100644 --- a/clang-tools-extra/clangd/Module.cpp +++ b/clang-tools-extra/clangd/FeatureModule.cpp @@ -1,4 +1,4 @@ -//===--- Module.cpp - Plugging features into clangd -----------------------===// +//===--- FeatureModule.cpp - Plugging features into clangd ----------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -6,27 +6,27 @@ // //===----------------------------------------------------------------------===// -#include "Module.h" +#include "FeatureModule.h" #include "support/Logger.h" namespace clang { namespace clangd { -void Module::initialize(const Facilities &F) { +void FeatureModule::initialize(const Facilities &F) { assert(!Fac.hasValue() && "Initialized twice"); Fac.emplace(F); } -Module::Facilities &Module::facilities() { +FeatureModule::Facilities &FeatureModule::facilities() { assert(Fac.hasValue() && "Not initialized yet"); return *Fac; } -bool ModuleSet::addImpl(void *Key, std::unique_ptr M, - const char *Source) { +bool FeatureModuleSet::addImpl(void *Key, std::unique_ptr M, + const char *Source) { if (!Map.try_emplace(Key, M.get()).second) { // Source should (usually) include the name of the concrete module type. - elog("Tried to register duplicate modules via {0}", Source); + elog("Tried to register duplicate feature modules via {0}", Source); return false; } Modules.push_back(std::move(M)); diff --git a/clang-tools-extra/clangd/Module.h b/clang-tools-extra/clangd/FeatureModule.h similarity index 71% rename from clang-tools-extra/clangd/Module.h rename to clang-tools-extra/clangd/FeatureModule.h index f660eff8565337f2443ec96b7d457177b9d6dad3..337fa24e945435d258e1037fdd3ae9cd9806095d 100644 --- a/clang-tools-extra/clangd/Module.h +++ b/clang-tools-extra/clangd/FeatureModule.h @@ -1,4 +1,4 @@ -//===--- Module.h - Plugging features into clangd -----------------*-C++-*-===// +//===--- FeatureModule.h - Plugging features into clangd ----------*-C++-*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -6,8 +6,8 @@ // //===----------------------------------------------------------------------===// -#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_MODULE_H -#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_MODULE_H +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_FEATUREMODULE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_FEATUREMODULE_H #include "support/Function.h" #include "support/Threading.h" @@ -26,11 +26,11 @@ class SymbolIndex; class ThreadsafeFS; class TUScheduler; -/// A Module contributes a vertical feature to clangd. +/// A FeatureModule contributes a vertical feature to clangd. /// /// The lifetime of a module is roughly: -/// - modules are created before the LSP server, in ClangdMain.cpp -/// - these modules are then passed to ClangdLSPServer in a ModuleSet +/// - feature modules are created before the LSP server, in ClangdMain.cpp +/// - these modules are then passed to ClangdLSPServer in a FeatureModuleSet /// - initializeLSP() is called when the editor calls initialize. // - initialize() is then called by ClangdServer as it is constructed. /// - module hooks can be called by the server at this point. @@ -39,31 +39,31 @@ class TUScheduler; /// FIXME: Block server shutdown until all the modules are idle. /// - When shutting down, ClangdServer will wait for all requests to /// finish, call stop(), and then blockUntilIdle(). -/// - modules will be destroyed after ClangdLSPServer is destroyed. +/// - feature modules will be destroyed after ClangdLSPServer is destroyed. /// -/// Modules are not threadsafe in general. A module's entrypoints are: +/// FeatureModules are not threadsafe in general. A module's entrypoints are: /// - method handlers registered in initializeLSP() -/// - public methods called directly via ClangdServer.getModule()->... -/// - specific overridable "hook" methods inherited from Module +/// - public methods called directly via ClangdServer.featureModule()->... +/// - specific overridable "hook" methods inherited from FeatureModule /// Unless otherwise specified, these are only called on the main thread. /// -/// Conventionally, standard modules live in the `clangd` namespace, and other -/// exposed details live in a sub-namespace. -class Module { +/// Conventionally, standard feature modules live in the `clangd` namespace, +/// and other exposed details live in a sub-namespace. +class FeatureModule { public: - virtual ~Module() { + virtual ~FeatureModule() { /// Perform shutdown sequence on destruction in case the ClangdServer was /// never initialized. Usually redundant, but shutdown is idempotent. stop(); blockUntilIdle(Deadline::infinity()); } - /// Called by the server to connect this module to LSP. + /// Called by the server to connect this feature module to LSP. /// The module should register the methods/notifications/commands it handles, /// and update the server capabilities to advertise them. /// /// This is only called if the module is running in ClangdLSPServer! - /// Modules with a public interface should satisfy it without LSP bindings. + /// FeatureModules with a public interface should work without LSP bindings. virtual void initializeLSP(LSPBinder &Bind, const llvm::json::Object &ClientCaps, llvm::json::Object &ServerCaps) {} @@ -87,7 +87,7 @@ public: /// Waits until the module is idle (no background work) or a deadline expires. /// In general all modules should eventually go idle, though it may take a /// long time (e.g. background indexing). - /// Modules should go idle quickly if stop() has been called. + /// FeatureModules should go idle quickly if stop() has been called. /// Called by the server when shutting down, and also by tests. virtual bool blockUntilIdle(Deadline) { return true; } @@ -101,7 +101,7 @@ protected: /// The filesystem is used to read source files on disk. const ThreadsafeFS &fs() { return facilities().FS; } - /// Types of function objects that modules use for outgoing calls. + /// Types of function objects that feature modules use for outgoing calls. /// (Bound throuh LSPBinder, made available here for convenience). template using OutgoingNotification = llvm::unique_function; @@ -112,28 +112,28 @@ private: llvm::Optional Fac; }; -/// A ModuleSet is a collection of modules installed in clangd. +/// A FeatureModuleSet is a collection of feature modules installed in clangd. /// -/// Modules can be looked up by type, or used through the Module interface. +/// Modules can be looked up by type, or used via the FeatureModule interface. /// This allows individual modules to expose a public API. -/// For this reason, there can be only one module of each type. +/// For this reason, there can be only one feature module of each type. /// -/// ModuleSet owns the modules. It is itself owned by main, not ClangdServer. -class ModuleSet { - std::vector> Modules; - llvm::DenseMap Map; +/// The set owns the modules. It is itself owned by main, not ClangdServer. +class FeatureModuleSet { + std::vector> Modules; + llvm::DenseMap Map; template struct ID { - static_assert(std::is_base_of::value && + static_assert(std::is_base_of::value && std::is_final::value, "Modules must be final classes derived from clangd::Module"); static int Key; }; - bool addImpl(void *Key, std::unique_ptr, const char *Source); + bool addImpl(void *Key, std::unique_ptr, const char *Source); public: - ModuleSet() = default; + FeatureModuleSet() = default; using iterator = llvm::pointee_iterator; using const_iterator = @@ -150,11 +150,11 @@ public: return static_cast(Map.lookup(&ID::Key)); } template const Mod *get() const { - return const_cast(this)->get(); + return const_cast(this)->get(); } }; -template int ModuleSet::ID::Key; +template int FeatureModuleSet::ID::Key; } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/FindSymbols.cpp b/clang-tools-extra/clangd/FindSymbols.cpp index ca4b8dafa5a02402364376c24e8f7d382789a717..bda5dcadf12eefe12ae64b1d53a98b39cbd13b78 100644 --- a/clang-tools-extra/clangd/FindSymbols.cpp +++ b/clang-tools-extra/clangd/FindSymbols.cpp @@ -88,7 +88,7 @@ llvm::Expected> getWorkspaceSymbols(llvm::StringRef Query, int Limit, const SymbolIndex *const Index, llvm::StringRef HintPath) { std::vector Result; - if (Query.empty() || !Index) + if (!Index) return Result; // Lookup for qualified names are performed as: @@ -172,6 +172,26 @@ getWorkspaceSymbols(llvm::StringRef Query, int Limit, } namespace { +std::string getSymbolName(ASTContext &Ctx, const NamedDecl &ND) { + // Print `MyClass(Category)` instead of `Category` and `MyClass()` instead + // of `anonymous`. + if (const auto *Container = dyn_cast(&ND)) + return printObjCContainer(*Container); + // Differentiate between class and instance methods: print `-foo` instead of + // `foo` and `+sharedInstance` instead of `sharedInstance`. + if (const auto *Method = dyn_cast(&ND)) { + std::string Name; + llvm::raw_string_ostream OS(Name); + + OS << (Method->isInstanceMethod() ? '-' : '+'); + Method->getSelector().print(OS); + + OS.flush(); + return Name; + } + return printName(Ctx, ND); +} + std::string getSymbolDetail(ASTContext &Ctx, const NamedDecl &ND) { PrintingPolicy P(Ctx.getPrintingPolicy()); P.SuppressScope = true; @@ -220,7 +240,7 @@ llvm::Optional declToSym(ASTContext &Ctx, const NamedDecl &ND) { SymbolKind SK = indexSymbolKindToSymbolKind(SymInfo.Kind); DocumentSymbol SI; - SI.name = printName(Ctx, ND); + SI.name = getSymbolName(Ctx, ND); SI.kind = SK; SI.deprecated = ND.isDeprecated(); SI.range = Range{sourceLocToPosition(SM, SymbolRange->getBegin()), @@ -269,21 +289,103 @@ llvm::Optional declToSym(ASTContext &Ctx, const NamedDecl &ND) { /// - visiting decls is actually simple, so we don't hit the complicated /// cases that RAV mostly helps with (types, expressions, etc.) class DocumentOutline { + // A DocumentSymbol we're constructing. + // We use this instead of DocumentSymbol directly so that we can keep track + // of the nodes we insert for macros. + class SymBuilder { + std::vector Children; + DocumentSymbol Symbol; // Symbol.children is empty, use Children instead. + // Macro expansions that this node or its parents are associated with. + // (Thus we will never create further children for these expansions). + llvm::SmallVector EnclosingMacroLoc; + + public: + DocumentSymbol build() && { + for (SymBuilder &C : Children) { + Symbol.children.push_back(std::move(C).build()); + // Expand range to ensure children nest properly, which editors expect. + // This can fix some edge-cases in the AST, but is vital for macros. + // A macro expansion "contains" AST node if it covers the node's primary + // location, but it may not span the node's whole range. + Symbol.range.start = + std::min(Symbol.range.start, Symbol.children.back().range.start); + Symbol.range.end = + std::max(Symbol.range.end, Symbol.children.back().range.end); + } + return std::move(Symbol); + } + + // Add a symbol as a child of the current one. + SymBuilder &addChild(DocumentSymbol S) { + Children.emplace_back(); + Children.back().EnclosingMacroLoc = EnclosingMacroLoc; + Children.back().Symbol = std::move(S); + return Children.back(); + } + + // Get an appropriate container for children of this symbol that were + // expanded from a macro (whose spelled name is Tok). + // + // This may return: + // - a macro symbol child of this (either new or previously created) + // - this scope itself, if it *is* the macro symbol or is nested within it + SymBuilder &inMacro(const syntax::Token &Tok, const SourceManager &SM, + llvm::Optional Exp) { + if (llvm::is_contained(EnclosingMacroLoc, Tok.location())) + return *this; + // If there's an existing child for this macro, we expect it to be last. + if (!Children.empty() && !Children.back().EnclosingMacroLoc.empty() && + Children.back().EnclosingMacroLoc.back() == Tok.location()) + return Children.back(); + + DocumentSymbol Sym; + Sym.name = Tok.text(SM).str(); + Sym.kind = SymbolKind::Null; // There's no suitable kind! + Sym.range = Sym.selectionRange = + halfOpenToRange(SM, Tok.range(SM).toCharRange(SM)); + + // FIXME: Exp is currently unavailable for nested expansions. + if (Exp) { + // Full range covers the macro args. + Sym.range = halfOpenToRange(SM, CharSourceRange::getCharRange( + Exp->Spelled.front().location(), + Exp->Spelled.back().endLocation())); + // Show macro args as detail. + llvm::raw_string_ostream OS(Sym.detail); + const syntax::Token *Prev = nullptr; + for (const auto &Tok : Exp->Spelled.drop_front()) { + // Don't dump arbitrarily long macro args. + if (OS.tell() > 80) { + OS << " ...)"; + break; + } + if (Prev && Prev->endLocation() != Tok.location()) + OS << ' '; + OS << Tok.text(SM); + Prev = &Tok; + } + } + SymBuilder &Child = addChild(std::move(Sym)); + Child.EnclosingMacroLoc.push_back(Tok.location()); + return Child; + } + }; + public: DocumentOutline(ParsedAST &AST) : AST(AST) {} /// Builds the document outline for the generated AST. std::vector build() { - std::vector Results; + SymBuilder DummyRoot; for (auto &TopLevel : AST.getLocalTopLevelDecls()) - traverseDecl(TopLevel, Results); - return Results; + traverseDecl(TopLevel, DummyRoot); + return std::move(std::move(DummyRoot).build().children); } private: enum class VisitKind { No, OnlyDecl, OnlyChildren, DeclAndChildren }; - void traverseDecl(Decl *D, std::vector &Results) { + void traverseDecl(Decl *D, SymBuilder &Parent) { // Skip symbols which do not originate from the main file. if (!isInsideMainFile(D->getLocation(), AST.getSourceManager())) return; @@ -299,27 +401,76 @@ private: return; if (Visit == VisitKind::OnlyChildren) - return traverseChildren(D, Results); + return traverseChildren(D, Parent); auto *ND = llvm::cast(D); auto Sym = declToSym(AST.getASTContext(), *ND); if (!Sym) return; - Results.push_back(std::move(*Sym)); + SymBuilder &MacroParent = possibleMacroContainer(D->getLocation(), Parent); + SymBuilder &Child = MacroParent.addChild(std::move(*Sym)); if (Visit == VisitKind::OnlyDecl) return; assert(Visit == VisitKind::DeclAndChildren && "Unexpected VisitKind"); - traverseChildren(ND, Results.back().children); + traverseChildren(ND, Child); + } + + // Determines where a decl should appear in the DocumentSymbol hierarchy. + // + // This is usually a direct child of the relevant AST parent. + // But we may also insert nodes for macros. Given: + // #define DECLARE_INT(V) int v; + // namespace a { DECLARE_INT(x) } + // We produce: + // Namespace a + // Macro DECLARE_INT(x) + // Variable x + // + // In the absence of macros, this method simply returns Parent. + // Otherwise it may return a macro expansion node instead. + // Each macro only has at most one node in the hierarchy, even if it expands + // to multiple decls. + SymBuilder &possibleMacroContainer(SourceLocation TargetLoc, + SymBuilder &Parent) { + const auto &SM = AST.getSourceManager(); + // Look at the path of macro-callers from the token to the main file. + // Note that along these paths we see the "outer" macro calls first. + SymBuilder *CurParent = &Parent; + for (SourceLocation Loc = TargetLoc; Loc.isMacroID(); + Loc = SM.getImmediateMacroCallerLoc(Loc)) { + // Find the virtual macro body that our token is being substituted into. + FileID MacroBody; + if (SM.isMacroArgExpansion(Loc)) { + // Loc is part of a macro arg being substituted into a macro body. + MacroBody = SM.getFileID(SM.getImmediateExpansionRange(Loc).getBegin()); + } else { + // Loc is already in the macro body. + MacroBody = SM.getFileID(Loc); + } + // The macro body is being substituted for a macro expansion, whose + // first token is the name of the macro. + SourceLocation MacroName = + SM.getSLocEntry(MacroBody).getExpansion().getExpansionLocStart(); + // Only include the macro expansion in the outline if it was written + // directly in the main file, rather than expanded from another macro. + if (!MacroName.isValid() || !MacroName.isFileID()) + continue; + // All conditions satisfied, add the macro. + if (auto *Tok = AST.getTokens().spelledTokenAt(MacroName)) + CurParent = &CurParent->inMacro( + *Tok, SM, AST.getTokens().expansionStartingAt(Tok)); + } + return *CurParent; } - void traverseChildren(Decl *D, std::vector &Results) { + void traverseChildren(Decl *D, SymBuilder &Builder) { auto *Scope = llvm::dyn_cast(D); if (!Scope) return; for (auto *C : Scope->decls()) - traverseDecl(C, Results); + traverseDecl(C, Builder); } VisitKind shouldVisit(Decl *D) { diff --git a/clang-tools-extra/clangd/FindTarget.cpp b/clang-tools-extra/clangd/FindTarget.cpp index 2dabae47b51940718b39615c121595042d13ad1e..16433151d902fa0b0fb5f203d51c8a0cba81827d 100644 --- a/clang-tools-extra/clangd/FindTarget.cpp +++ b/clang-tools-extra/clangd/FindTarget.cpp @@ -12,6 +12,7 @@ #include "support/Logger.h" #include "clang/AST/ASTTypeTraits.h" #include "clang/AST/Decl.h" +#include "clang/AST/DeclBase.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclTemplate.h" #include "clang/AST/DeclVisitor.h" @@ -633,6 +634,61 @@ llvm::SmallVector refInDecl(const Decl *D, /*IsDecl=*/false, {DG->getDeducedTemplate()}}); } + + void VisitObjCMethodDecl(const ObjCMethodDecl *OMD) { + // The name may have several tokens, we can only report the first. + Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(), + OMD->getSelectorStartLoc(), + /*IsDecl=*/true, + {OMD}}); + } + + void visitProtocolList( + llvm::iterator_range Protocols, + llvm::iterator_range Locations) { + for (const auto &P : llvm::zip(Protocols, Locations)) { + Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(), + std::get<1>(P), + /*IsDecl=*/false, + {std::get<0>(P)}}); + } + } + + void VisitObjCInterfaceDecl(const ObjCInterfaceDecl *OID) { + if (OID->isThisDeclarationADefinition()) + visitProtocolList(OID->protocols(), OID->protocol_locs()); + Base::VisitObjCInterfaceDecl(OID); // Visit the interface's name. + } + + void VisitObjCCategoryDecl(const ObjCCategoryDecl *OCD) { + visitProtocolList(OCD->protocols(), OCD->protocol_locs()); + // getLocation is the extended class's location, not the category's. + Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(), + OCD->getLocation(), + /*IsDecl=*/false, + {OCD->getClassInterface()}}); + Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(), + OCD->getCategoryNameLoc(), + /*IsDecl=*/true, + {OCD}}); + } + + void VisitObjCCategoryImplDecl(const ObjCCategoryImplDecl *OCID) { + Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(), + OCID->getLocation(), + /*IsDecl=*/false, + {OCID->getClassInterface()}}); + Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(), + OCID->getCategoryNameLoc(), + /*IsDecl=*/true, + {OCID->getCategoryDecl()}}); + } + + void VisitObjCProtocolDecl(const ObjCProtocolDecl *OPD) { + if (OPD->isThisDeclarationADefinition()) + visitProtocolList(OPD->protocols(), OPD->protocol_locs()); + Base::VisitObjCProtocolDecl(OPD); // Visit the protocol's name. + } }; Visitor V{Resolver}; @@ -711,25 +767,31 @@ llvm::SmallVector refInStmt(const Stmt *S, explicitReferenceTargets(DynTypedNode::create(*E), {}, Resolver)}); } + void VisitObjCMessageExpr(const ObjCMessageExpr *E) { + // The name may have several tokens, we can only report the first. + Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(), + E->getSelectorStartLoc(), + /*IsDecl=*/false, + {E->getMethodDecl()}}); + } + void VisitDesignatedInitExpr(const DesignatedInitExpr *DIE) { for (const DesignatedInitExpr::Designator &D : DIE->designators()) { if (!D.isFieldDesignator()) continue; - llvm::SmallVector Targets; - if (D.getField()) - Targets.push_back(D.getField()); - Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(), D.getFieldLoc(), - /*IsDecl=*/false, std::move(Targets)}); + Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(), + D.getFieldLoc(), + /*IsDecl=*/false, + {D.getField()}}); } } void VisitGotoStmt(const GotoStmt *GS) { - llvm::SmallVector Targets; - if (const auto *L = GS->getLabel()) - Targets.push_back(L); - Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(), GS->getLabelLoc(), - /*IsDecl=*/false, std::move(Targets)}); + Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(), + GS->getLabelLoc(), + /*IsDecl=*/false, + {GS->getLabel()}}); } void VisitLabelStmt(const LabelStmt *LS) { @@ -826,6 +888,16 @@ refInTypeLoc(TypeLoc L, const HeuristicResolver *Resolver) { /*IsDecl=*/false, {L.getTypedefNameDecl()}}; } + + void VisitObjCInterfaceTypeLoc(ObjCInterfaceTypeLoc L) { + Ref = ReferenceLoc{NestedNameSpecifierLoc(), + L.getNameLoc(), + /*IsDecl=*/false, + {L.getIFaceDecl()}}; + } + + // FIXME: add references to protocols in ObjCObjectTypeLoc and maybe + // ObjCObjectPointerTypeLoc. }; Visitor V{Resolver}; @@ -882,17 +954,15 @@ public: // TemplateArgumentLoc is the only way to get locations for references to // template template parameters. bool TraverseTemplateArgumentLoc(TemplateArgumentLoc A) { - llvm::SmallVector Targets; switch (A.getArgument().getKind()) { case TemplateArgument::Template: case TemplateArgument::TemplateExpansion: - if (const auto *D = A.getArgument() - .getAsTemplateOrTemplatePattern() - .getAsTemplateDecl()) - Targets.push_back(D); reportReference(ReferenceLoc{A.getTemplateQualifierLoc(), A.getTemplateNameLoc(), - /*IsDecl=*/false, Targets}, + /*IsDecl=*/false, + {A.getArgument() + .getAsTemplateOrTemplatePattern() + .getAsTemplateDecl()}}, DynTypedNode::create(A.getArgument())); break; case TemplateArgument::Declaration: @@ -975,11 +1045,14 @@ private: } void visitNode(DynTypedNode N) { - for (const auto &R : explicitReference(N)) - reportReference(R, N); + for (auto &R : explicitReference(N)) + reportReference(std::move(R), N); } - void reportReference(const ReferenceLoc &Ref, DynTypedNode N) { + void reportReference(ReferenceLoc &&Ref, DynTypedNode N) { + // Strip null targets that can arise from invalid code. + // (This avoids having to check for null everywhere we insert) + llvm::erase_value(Ref.Targets, nullptr); // Our promise is to return only references from the source code. If we lack // location information, skip these nodes. // Normally this should not happen in practice, unless there are bugs in the diff --git a/clang-tools-extra/clangd/Headers.h b/clang-tools-extra/clangd/Headers.h index fd9db5562813fa7c563d41efe22c049ee700b5ec..f4ca364880c411a32638e3bc56238c73721f65b1 100644 --- a/clang-tools-extra/clangd/Headers.h +++ b/clang-tools-extra/clangd/Headers.h @@ -114,6 +114,9 @@ class IncludeStructure { public: std::vector MainFileIncludes; + // Return all transitively reachable files. + llvm::ArrayRef allHeaders() const { return RealPathNames; } + // Return all transitively reachable files, and their minimum include depth. // All transitive includes (absolute paths), with their minimum include depth. // Root --> 0, #included file --> 1, etc. diff --git a/clang-tools-extra/clangd/Hover.cpp b/clang-tools-extra/clangd/Hover.cpp index 82c3ccbab47c68ff4f733d8f4dc5304bc16e97b6..f9de9108355813d3b9bc652e8344d382cb3a17e2 100644 --- a/clang-tools-extra/clangd/Hover.cpp +++ b/clang-tools-extra/clangd/Hover.cpp @@ -43,6 +43,7 @@ #include "llvm/ADT/StringRef.h" #include "llvm/Support/Casting.h" #include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/Format.h" #include "llvm/Support/raw_ostream.h" #include @@ -347,6 +348,19 @@ void fillFunctionTypeAndParams(HoverInfo &HI, const Decl *D, // FIXME: handle variadics. } +// Non-negative numbers are printed using min digits +// 0 => 0x0 +// 100 => 0x64 +// Negative numbers are sign-extended to 32/64 bits +// -2 => 0xfffffffe +// -2^32 => 0xfffffffeffffffff +static llvm::FormattedNumber printHex(const llvm::APSInt &V) { + uint64_t Bits = V.getExtValue(); + if (V.isNegative() && V.getMinSignedBits() <= 32) + return llvm::format_hex(uint32_t(Bits), 0); + return llvm::format_hex(Bits, 0); +} + llvm::Optional printExprValue(const Expr *E, const ASTContext &Ctx) { // InitListExpr has two forms, syntactic and semantic. They are the same thing @@ -381,8 +395,17 @@ llvm::Optional printExprValue(const Expr *E, for (const EnumConstantDecl *ECD : T->castAs()->getDecl()->enumerators()) if (ECD->getInitVal() == Val) - return llvm::formatv("{0} ({1})", ECD->getNameAsString(), Val).str(); + return llvm::formatv("{0} ({1})", ECD->getNameAsString(), + printHex(Constant.Val.getInt())) + .str(); } + // Show hex value of integers if they're at least 10 (or negative!) + if (T->isIntegralOrEnumerationType() && + Constant.Val.getInt().getMinSignedBits() <= 64 && + Constant.Val.getInt().uge(10)) + return llvm::formatv("{0} ({1})", Constant.Val.getAsString(Ctx, T), + printHex(Constant.Val.getInt())) + .str(); return Constant.Val.getAsString(Ctx, T); } diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp index 525da502b692b203125c0ad286529f16b0ac4e40..b3ff124df4de7534c986e3ffeef43d0f78a00ba5 100644 --- a/clang-tools-extra/clangd/Protocol.cpp +++ b/clang-tools-extra/clangd/Protocol.cpp @@ -403,6 +403,10 @@ bool fromJSON(const llvm::json::Value &Params, ClientCapabilities &R, } } } + if (auto *SemanticTokens = Workspace->getObject("semanticTokens")) { + if (auto RefreshSupport = SemanticTokens->getBoolean("refreshSupport")) + R.SemanticTokenRefreshSupport = *RefreshSupport; + } } if (auto *Window = O->getObject("window")) { if (auto WorkDoneProgress = Window->getBoolean("workDoneProgress")) diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h index f918183716d28d1229d3647e4dbcb0cb50ca3060..c6074abcb04e9e7718674a4c5bfbeb2a8048d04a 100644 --- a/clang-tools-extra/clangd/Protocol.h +++ b/clang-tools-extra/clangd/Protocol.h @@ -261,6 +261,7 @@ enum class TraceLevel { bool fromJSON(const llvm::json::Value &E, TraceLevel &Out, llvm::json::Path); struct NoParams {}; +inline llvm::json::Value toJSON(const NoParams &) { return nullptr; } inline bool fromJSON(const llvm::json::Value &, NoParams &, llvm::json::Path) { return true; } @@ -473,6 +474,10 @@ struct ClientCapabilities { /// This is a clangd extension. /// window.implicitWorkDoneProgressCreate bool ImplicitProgressCreation = false; + + /// Whether the client implementation supports a refresh request sent from the + /// server to the client. + bool SemanticTokenRefreshSupport = false; }; bool fromJSON(const llvm::json::Value &, ClientCapabilities &, llvm::json::Path); @@ -981,7 +986,7 @@ struct DocumentSymbol { SymbolKind kind; /// Indicates if this symbol is deprecated. - bool deprecated; + bool deprecated = false; /// The range enclosing this symbol not including leading/trailing whitespace /// but everything else like comments. This information is typically used to diff --git a/clang-tools-extra/clangd/Quality.cpp b/clang-tools-extra/clangd/Quality.cpp index b49392bc7d043fc10e92a05301da554e65c00524..99421009c71ca88f5a3dc576b779e8acfaffdec8 100644 --- a/clang-tools-extra/clangd/Quality.cpp +++ b/clang-tools-extra/clangd/Quality.cpp @@ -580,12 +580,16 @@ evaluateDecisionForest(const SymbolQualitySignals &Quality, // multiplciative boost (like NameMatch). This allows us to weigh the // prediciton score and NameMatch appropriately. Scores.ExcludingName = pow(Base, Evaluate(E)); - // NeedsFixIts is not part of the DecisionForest as generating training - // data that needs fixits is not-feasible. + // Following cases are not part of the generated training dataset: + // - Symbols with `NeedsFixIts`. + // - Forbidden symbols. + // - Keywords: Dataset contains only macros and decls. if (Relevance.NeedsFixIts) Scores.ExcludingName *= 0.5; if (Relevance.Forbidden) Scores.ExcludingName *= 0; + if (Quality.Category == SymbolQualitySignals::Keyword) + Scores.ExcludingName *= 4; // NameMatch should be a multiplier on total score to support rescoring. Scores.Total = Relevance.NameMatch * Scores.ExcludingName; diff --git a/clang-tools-extra/clangd/SemanticHighlighting.cpp b/clang-tools-extra/clangd/SemanticHighlighting.cpp index da8ee7e41ebd6c94c7ac565b3c3a9d62774a55af..0b4965c427150458c813b7b400ee4c0aea291ecc 100644 --- a/clang-tools-extra/clangd/SemanticHighlighting.cpp +++ b/clang-tools-extra/clangd/SemanticHighlighting.cpp @@ -40,11 +40,28 @@ namespace { /// Some names are not written in the source code and cannot be highlighted, /// e.g. anonymous classes. This function detects those cases. bool canHighlightName(DeclarationName Name) { - if (Name.getNameKind() == DeclarationName::CXXConstructorName || - Name.getNameKind() == DeclarationName::CXXUsingDirective) + switch (Name.getNameKind()) { + case DeclarationName::Identifier: { + auto *II = Name.getAsIdentifierInfo(); + return II && !II->getName().empty(); + } + case DeclarationName::CXXConstructorName: + case DeclarationName::CXXDestructorName: return true; - auto *II = Name.getAsIdentifierInfo(); - return II && !II->getName().empty(); + case DeclarationName::ObjCZeroArgSelector: + case DeclarationName::ObjCOneArgSelector: + case DeclarationName::ObjCMultiArgSelector: + // Multi-arg selectors need special handling, and we handle 0/1 arg + // selectors there too. + return false; + case DeclarationName::CXXConversionFunctionName: + case DeclarationName::CXXOperatorName: + case DeclarationName::CXXDeductionGuideName: + case DeclarationName::CXXLiteralOperatorName: + case DeclarationName::CXXUsingDirective: + return false; + } + llvm_unreachable("invalid name kind"); } llvm::Optional kindForType(const Type *TP); @@ -73,13 +90,20 @@ llvm::Optional kindForDecl(const NamedDecl *D) { return llvm::None; return HighlightingKind::Class; } - if (isa(D) || isa(D) || - isa(D)) + if (isa(D)) return HighlightingKind::Class; + if (isa(D)) + return HighlightingKind::Interface; + if (isa(D)) + return HighlightingKind::Namespace; if (auto *MD = dyn_cast(D)) return MD->isStatic() ? HighlightingKind::StaticMethod : HighlightingKind::Method; - if (isa(D)) + if (auto *OMD = dyn_cast(D)) + return OMD->isClassMethod() ? HighlightingKind::StaticMethod + : HighlightingKind::Method; + if (isa(D)) return HighlightingKind::Field; if (isa(D)) return HighlightingKind::Enum; @@ -87,11 +111,14 @@ llvm::Optional kindForDecl(const NamedDecl *D) { return HighlightingKind::EnumConstant; if (isa(D)) return HighlightingKind::Parameter; - if (auto *VD = dyn_cast(D)) + if (auto *VD = dyn_cast(D)) { + if (isa(VD)) // e.g. ObjC Self + return llvm::None; return VD->isStaticDataMember() ? HighlightingKind::StaticField : VD->isLocalVarDecl() ? HighlightingKind::LocalVariable : HighlightingKind::Variable; + } if (const auto *BD = dyn_cast(D)) return BD->getDeclContext()->isFunctionOrMethod() ? HighlightingKind::LocalVariable @@ -115,6 +142,8 @@ llvm::Optional kindForType(const Type *TP) { return HighlightingKind::Primitive; if (auto *TD = dyn_cast(TP)) return kindForDecl(TD->getDecl()); + if (isa(TP)) + return HighlightingKind::Class; if (auto *TD = TP->getAsTagDecl()) return kindForDecl(TD); return llvm::None; @@ -439,6 +468,36 @@ public: return true; } + // We handle objective-C selectors specially, because one reference can + // cover several non-contiguous tokens. + void highlightObjCSelector(const ArrayRef &Locs, bool Decl, + bool Class) { + HighlightingKind Kind = + Class ? HighlightingKind::StaticMethod : HighlightingKind::Method; + for (SourceLocation Part : Locs) { + auto &Tok = + H.addToken(Part, Kind).addModifier(HighlightingModifier::ClassScope); + if (Decl) + Tok.addModifier(HighlightingModifier::Declaration); + if (Class) + Tok.addModifier(HighlightingModifier::Static); + } + } + + bool VisitObjCMethodDecl(ObjCMethodDecl *OMD) { + llvm::SmallVector Locs; + OMD->getSelectorLocs(Locs); + highlightObjCSelector(Locs, /*Decl=*/true, OMD->isClassMethod()); + return true; + } + + bool VisitObjCMessageExpr(ObjCMessageExpr *OME) { + llvm::SmallVector Locs; + OME->getSelectorLocs(Locs); + highlightObjCSelector(Locs, /*Decl=*/false, OME->isClassMessage()); + return true; + } + bool VisitOverloadExpr(OverloadExpr *E) { if (!E->decls().empty()) return true; // handled by findExplicitReferences. @@ -602,6 +661,8 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, HighlightingKind K) { return OS << "StaticField"; case HighlightingKind::Class: return OS << "Class"; + case HighlightingKind::Interface: + return OS << "Interface"; case HighlightingKind::Enum: return OS << "Enum"; case HighlightingKind::EnumConstant: @@ -695,6 +756,8 @@ llvm::StringRef toSemanticTokenType(HighlightingKind Kind) { return "property"; case HighlightingKind::Class: return "class"; + case HighlightingKind::Interface: + return "interface"; case HighlightingKind::Enum: return "enum"; case HighlightingKind::EnumConstant: diff --git a/clang-tools-extra/clangd/SemanticHighlighting.h b/clang-tools-extra/clangd/SemanticHighlighting.h index e4c36ab5261ef392d711b8d480d1ceb7b2ab58ab..40b315c679a4afad6c4d509bd10ed8d214025bbb 100644 --- a/clang-tools-extra/clangd/SemanticHighlighting.h +++ b/clang-tools-extra/clangd/SemanticHighlighting.h @@ -37,6 +37,7 @@ enum class HighlightingKind { Field, StaticField, Class, + Interface, Enum, EnumConstant, Typedef, diff --git a/clang-tools-extra/clangd/SourceCode.cpp b/clang-tools-extra/clangd/SourceCode.cpp index c0ccf21527509b8cae33a5b0dff4521b2c42b815..8faed3e046aa399d8b7ebf60f8b7f2c32c54f886 100644 --- a/clang-tools-extra/clangd/SourceCode.cpp +++ b/clang-tools-extra/clangd/SourceCode.cpp @@ -1053,6 +1053,49 @@ llvm::Error reformatEdit(Edit &E, const format::FormatStyle &Style) { return llvm::Error::success(); } +llvm::Error applyChange(std::string &Contents, + const TextDocumentContentChangeEvent &Change) { + if (!Change.range) { + Contents = Change.text; + return llvm::Error::success(); + } + + const Position &Start = Change.range->start; + llvm::Expected StartIndex = positionToOffset(Contents, Start, false); + if (!StartIndex) + return StartIndex.takeError(); + + const Position &End = Change.range->end; + llvm::Expected EndIndex = positionToOffset(Contents, End, false); + if (!EndIndex) + return EndIndex.takeError(); + + if (*EndIndex < *StartIndex) + return error(llvm::errc::invalid_argument, + "Range's end position ({0}) is before start position ({1})", + End, Start); + + // Since the range length between two LSP positions is dependent on the + // contents of the buffer we compute the range length between the start and + // end position ourselves and compare it to the range length of the LSP + // message to verify the buffers of the client and server are in sync. + + // EndIndex and StartIndex are in bytes, but Change.rangeLength is in UTF-16 + // code units. + ssize_t ComputedRangeLength = + lspLength(Contents.substr(*StartIndex, *EndIndex - *StartIndex)); + + if (Change.rangeLength && ComputedRangeLength != *Change.rangeLength) + return error(llvm::errc::invalid_argument, + "Change's rangeLength ({0}) doesn't match the " + "computed range length ({1}).", + *Change.rangeLength, ComputedRangeLength); + + Contents.replace(*StartIndex, *EndIndex - *StartIndex, Change.text); + + return llvm::Error::success(); +} + EligibleRegion getEligiblePoints(llvm::StringRef Code, llvm::StringRef FullyQualifiedName, const LangOptions &LangOpts) { diff --git a/clang-tools-extra/clangd/SourceCode.h b/clang-tools-extra/clangd/SourceCode.h index be78e2f86436cdb9cae149312272204734c915c2..7628fe138d939e1401dd87e2473bb2158df8ec3e 100644 --- a/clang-tools-extra/clangd/SourceCode.h +++ b/clang-tools-extra/clangd/SourceCode.h @@ -203,6 +203,10 @@ using FileEdits = llvm::StringMap; /// Replacements to formatted ones if succeeds. llvm::Error reformatEdit(Edit &E, const format::FormatStyle &Style); +/// Apply an incremental update to a text document. +llvm::Error applyChange(std::string &Contents, + const TextDocumentContentChangeEvent &Change); + /// Collects identifiers with counts in the source code. llvm::StringMap collectIdentifiers(llvm::StringRef Content, const format::FormatStyle &Style); diff --git a/clang-tools-extra/clangd/TUScheduler.cpp b/clang-tools-extra/clangd/TUScheduler.cpp index 01b583fe64f3185f55d7e1adaf09bc45bdaeac57..435deb6ec162aff11641919846c608a3dbce4e2b 100644 --- a/clang-tools-extra/clangd/TUScheduler.cpp +++ b/clang-tools-extra/clangd/TUScheduler.cpp @@ -70,12 +70,14 @@ #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringRef.h" +#include "llvm/Support/Allocator.h" #include "llvm/Support/Errc.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/FormatVariadic.h" #include "llvm/Support/Path.h" #include "llvm/Support/Threading.h" #include +#include #include #include #include @@ -179,7 +181,132 @@ private: std::vector LRU; /* GUARDED_BY(Mut) */ }; +/// A map from header files to an opened "proxy" file that includes them. +/// If you open the header, the compile command from the proxy file is used. +/// +/// This inclusion information could also naturally live in the index, but there +/// are advantages to using open files instead: +/// - it's easier to achieve a *stable* choice of proxy, which is important +/// to avoid invalidating the preamble +/// - context-sensitive flags for libraries with multiple configurations +/// (e.g. C++ stdlib sensitivity to -std version) +/// - predictable behavior, e.g. guarantees that go-to-def landing on a header +/// will have a suitable command available +/// - fewer scaling problems to solve (project include graphs are big!) +/// +/// Implementation details: +/// - We only record this for mainfiles where the command was trustworthy +/// (i.e. not inferred). This avoids a bad inference "infecting" other files. +/// - Once we've picked a proxy file for a header, we stick with it until the +/// proxy file is invalidated *and* a new candidate proxy file is built. +/// Switching proxies is expensive, as the compile flags will (probably) +/// change and therefore we'll end up rebuilding the header's preamble. +/// - We don't capture the actual compile command, but just the filename we +/// should query to get it. This avoids getting out of sync with the CDB. +/// +/// All methods are threadsafe. In practice, update() comes from preamble +/// threads, remove()s mostly from the main thread, and get() from ASTWorker. +/// Writes are rare and reads are cheap, so we don't expect much contention. +class TUScheduler::HeaderIncluderCache { + // We should be be a little careful how we store the include graph of open + // files, as each can have a large number of transitive headers. + // This representation is O(unique transitive source files). + llvm::BumpPtrAllocator Arena; + struct Association { + llvm::StringRef MainFile; + // Circular-linked-list of associations with the same mainFile. + // Null indicates that the mainfile was removed. + Association *Next; + }; + llvm::StringMap HeaderToMain; + llvm::StringMap MainToFirst; + std::atomic UsedBytes; // Updated after writes. + mutable std::mutex Mu; + + void invalidate(Association *First) { + Association *Current = First; + do { + Association *Next = Current->Next; + Current->Next = nullptr; + Current = Next; + } while (Current != First); + } + + // Create the circular list and return the head of it. + Association *associate(llvm::StringRef MainFile, + llvm::ArrayRef Headers) { + Association *First = nullptr, *Prev = nullptr; + for (const std::string &Header : Headers) { + auto &Assoc = HeaderToMain[Header]; + if (Assoc.Next) + continue; // Already has a valid association. + + Assoc.MainFile = MainFile; + Assoc.Next = Prev; + Prev = &Assoc; + if (!First) + First = &Assoc; + } + if (First) + First->Next = Prev; + return First; + } + + void updateMemoryUsage() { + auto StringMapHeap = [](const auto &Map) { + // StringMap stores the hashtable on the heap. + // It contains pointers to the entries, and a hashcode for each. + return Map.getNumBuckets() * (sizeof(void *) + sizeof(unsigned)); + }; + size_t Usage = Arena.getTotalMemory() + StringMapHeap(MainToFirst) + + StringMapHeap(HeaderToMain) + sizeof(*this); + UsedBytes.store(Usage, std::memory_order_release); + } + +public: + HeaderIncluderCache() : HeaderToMain(Arena), MainToFirst(Arena) { + updateMemoryUsage(); + } + + // Associate each header with MainFile (unless already associated). + // Headers not in the list will have their associations removed. + void update(PathRef MainFile, llvm::ArrayRef Headers) { + std::lock_guard Lock(Mu); + auto It = MainToFirst.try_emplace(MainFile, nullptr); + Association *&First = It.first->second; + if (First) + invalidate(First); + First = associate(It.first->first(), Headers); + updateMemoryUsage(); + } + + // Mark MainFile as gone. + // This will *not* disassociate headers with MainFile immediately, but they + // will be eligible for association with other files that get update()d. + void remove(PathRef MainFile) { + std::lock_guard Lock(Mu); + Association *&First = MainToFirst[MainFile]; + if (First) + invalidate(First); + } + + /// Get the mainfile associated with Header, or the empty string if none. + std::string get(PathRef Header) const { + std::lock_guard Lock(Mu); + return HeaderToMain.lookup(Header).MainFile.str(); + } + + size_t getUsedBytes() const { + return UsedBytes.load(std::memory_order_acquire); + } +}; + namespace { + +bool isReliable(const tooling::CompileCommand &Cmd) { + return Cmd.Heuristic.empty(); +} + /// Threadsafe manager for updating a TUStatus and emitting it after each /// update. class SynchronizedTUStatus { @@ -221,10 +348,12 @@ class PreambleThread { public: PreambleThread(llvm::StringRef FileName, ParsingCallbacks &Callbacks, bool StorePreambleInMemory, bool RunSync, - SynchronizedTUStatus &Status, ASTWorker &AW) + SynchronizedTUStatus &Status, + TUScheduler::HeaderIncluderCache &HeaderIncluders, + ASTWorker &AW) : FileName(FileName), Callbacks(Callbacks), StoreInMemory(StorePreambleInMemory), RunSync(RunSync), Status(Status), - ASTPeer(AW) {} + ASTPeer(AW), HeaderIncluders(HeaderIncluders) {} /// It isn't guaranteed that each requested version will be built. If there /// are multiple update requests while building a preamble, only the last one @@ -351,6 +480,7 @@ private: SynchronizedTUStatus &Status; ASTWorker &ASTPeer; + TUScheduler::HeaderIncluderCache &HeaderIncluders; }; class ASTWorkerHandle; @@ -368,8 +498,10 @@ class ASTWorkerHandle; class ASTWorker { friend class ASTWorkerHandle; ASTWorker(PathRef FileName, const GlobalCompilationDatabase &CDB, - TUScheduler::ASTCache &LRUCache, Semaphore &Barrier, bool RunSync, - const TUScheduler::Options &Opts, ParsingCallbacks &Callbacks); + TUScheduler::ASTCache &LRUCache, + TUScheduler::HeaderIncluderCache &HeaderIncluders, + Semaphore &Barrier, bool RunSync, const TUScheduler::Options &Opts, + ParsingCallbacks &Callbacks); public: /// Create a new ASTWorker and return a handle to it. @@ -377,12 +509,12 @@ public: /// is null, all requests will be processed on the calling thread /// synchronously instead. \p Barrier is acquired when processing each /// request, it is used to limit the number of actively running threads. - static ASTWorkerHandle create(PathRef FileName, - const GlobalCompilationDatabase &CDB, - TUScheduler::ASTCache &IdleASTs, - AsyncTaskRunner *Tasks, Semaphore &Barrier, - const TUScheduler::Options &Opts, - ParsingCallbacks &Callbacks); + static ASTWorkerHandle + create(PathRef FileName, const GlobalCompilationDatabase &CDB, + TUScheduler::ASTCache &IdleASTs, + TUScheduler::HeaderIncluderCache &HeaderIncluders, + AsyncTaskRunner *Tasks, Semaphore &Barrier, + const TUScheduler::Options &Opts, ParsingCallbacks &Callbacks); ~ASTWorker(); void update(ParseInputs Inputs, WantDiagnostics, bool ContentChanged); @@ -473,6 +605,7 @@ private: /// Handles retention of ASTs. TUScheduler::ASTCache &IdleASTs; + TUScheduler::HeaderIncluderCache &HeaderIncluders; const bool RunSync; /// Time to wait after an update to see whether another update obsoletes it. const DebouncePolicy UpdateDebounce; @@ -571,14 +704,16 @@ private: std::shared_ptr Worker; }; -ASTWorkerHandle ASTWorker::create(PathRef FileName, - const GlobalCompilationDatabase &CDB, - TUScheduler::ASTCache &IdleASTs, - AsyncTaskRunner *Tasks, Semaphore &Barrier, - const TUScheduler::Options &Opts, - ParsingCallbacks &Callbacks) { - std::shared_ptr Worker(new ASTWorker( - FileName, CDB, IdleASTs, Barrier, /*RunSync=*/!Tasks, Opts, Callbacks)); +ASTWorkerHandle +ASTWorker::create(PathRef FileName, const GlobalCompilationDatabase &CDB, + TUScheduler::ASTCache &IdleASTs, + TUScheduler::HeaderIncluderCache &HeaderIncluders, + AsyncTaskRunner *Tasks, Semaphore &Barrier, + const TUScheduler::Options &Opts, + ParsingCallbacks &Callbacks) { + std::shared_ptr Worker( + new ASTWorker(FileName, CDB, IdleASTs, HeaderIncluders, Barrier, + /*RunSync=*/!Tasks, Opts, Callbacks)); if (Tasks) { Tasks->runAsync("ASTWorker:" + llvm::sys::path::filename(FileName), [Worker]() { Worker->run(); }); @@ -590,15 +725,17 @@ ASTWorkerHandle ASTWorker::create(PathRef FileName, } ASTWorker::ASTWorker(PathRef FileName, const GlobalCompilationDatabase &CDB, - TUScheduler::ASTCache &LRUCache, Semaphore &Barrier, - bool RunSync, const TUScheduler::Options &Opts, + TUScheduler::ASTCache &LRUCache, + TUScheduler::HeaderIncluderCache &HeaderIncluders, + Semaphore &Barrier, bool RunSync, + const TUScheduler::Options &Opts, ParsingCallbacks &Callbacks) - : IdleASTs(LRUCache), RunSync(RunSync), UpdateDebounce(Opts.UpdateDebounce), - FileName(FileName), ContextProvider(Opts.ContextProvider), CDB(CDB), - Callbacks(Callbacks), Barrier(Barrier), Done(false), - Status(FileName, Callbacks), + : IdleASTs(LRUCache), HeaderIncluders(HeaderIncluders), RunSync(RunSync), + UpdateDebounce(Opts.UpdateDebounce), FileName(FileName), + ContextProvider(Opts.ContextProvider), CDB(CDB), Callbacks(Callbacks), + Barrier(Barrier), Done(false), Status(FileName, Callbacks), PreamblePeer(FileName, Callbacks, Opts.StorePreamblesInMemory, RunSync, - Status, *this) { + Status, HeaderIncluders, *this) { // Set a fallback command because compile command can be accessed before // `Inputs` is initialized. Other fields are only used after initialization // from client inputs. @@ -625,10 +762,25 @@ void ASTWorker::update(ParseInputs Inputs, WantDiagnostics WantDiags, // environment to build the file, it would be nice if we could emit a // "PreparingBuild" status to inform users, it is non-trivial given the // current implementation. - if (auto Cmd = CDB.getCompileCommand(FileName)) - Inputs.CompileCommand = *Cmd; + auto Cmd = CDB.getCompileCommand(FileName); + // If we don't have a reliable command for this file, it may be a header. + // Try to find a file that includes it, to borrow its command. + if (!Cmd || !isReliable(*Cmd)) { + std::string ProxyFile = HeaderIncluders.get(FileName); + if (!ProxyFile.empty()) { + auto ProxyCmd = CDB.getCompileCommand(ProxyFile); + if (!ProxyCmd || !isReliable(*ProxyCmd)) { + // This command is supposed to be reliable! It's probably gone. + HeaderIncluders.remove(ProxyFile); + } else { + // We have a reliable command for an including file, use it. + Cmd = tooling::transferCompileCommand(std::move(*ProxyCmd), FileName); + } + } + } + if (Cmd) + Inputs.CompileCommand = std::move(*Cmd); else - // FIXME: consider using old command if it's not a fallback one. Inputs.CompileCommand = CDB.getFallbackCommand(FileName); bool InputsAreTheSame = @@ -780,6 +932,8 @@ void PreambleThread::build(Request Req) { Callbacks.onPreambleAST(FileName, Version, Ctx, std::move(PP), CanonIncludes); }); + if (LatestBuild && isReliable(LatestBuild->CompileCommand)) + HeaderIncluders.update(FileName, LatestBuild->Includes.allHeaders()); } void ASTWorker::updatePreamble(std::unique_ptr CI, @@ -1297,7 +1451,8 @@ TUScheduler::TUScheduler(const GlobalCompilationDatabase &CDB, : std::make_unique()), Barrier(Opts.AsyncThreadsCount), QuickRunBarrier(Opts.AsyncThreadsCount), IdleASTs( - std::make_unique(Opts.RetentionPolicy.MaxRetainedASTs)) { + std::make_unique(Opts.RetentionPolicy.MaxRetainedASTs)), + HeaderIncluders(std::make_unique()) { // Avoid null checks everywhere. if (!Opts.ContextProvider) { this->Opts.ContextProvider = [](llvm::StringRef) { @@ -1339,7 +1494,7 @@ bool TUScheduler::update(PathRef File, ParseInputs Inputs, if (!FD) { // Create a new worker to process the AST-related tasks. ASTWorkerHandle Worker = - ASTWorker::create(File, CDB, *IdleASTs, + ASTWorker::create(File, CDB, *IdleASTs, *HeaderIncluders, WorkerThreads ? WorkerThreads.getPointer() : nullptr, Barrier, Opts, *Callbacks); FD = std::unique_ptr( @@ -1358,13 +1513,10 @@ void TUScheduler::remove(PathRef File) { if (!Removed) elog("Trying to remove file from TUScheduler that is not tracked: {0}", File); -} - -llvm::StringMap TUScheduler::getAllFileContents() const { - llvm::StringMap Results; - for (auto &It : Files) - Results.try_emplace(It.getKey(), It.getValue()->Contents); - return Results; + // We don't call HeaderIncluders.remove(File) here. + // If we did, we'd avoid potentially stale header/mainfile associations. + // However, it would mean that closing a mainfile could invalidate the + // preamble of several open headers. } void TUScheduler::run(llvm::StringRef Name, llvm::StringRef Path, @@ -1516,6 +1668,7 @@ void TUScheduler::profile(MemoryTree &MT) const { .addUsage(Opts.StorePreamblesInMemory ? Elem.second.UsedBytesPreamble : 0); MT.detail(Elem.first()).child("ast").addUsage(Elem.second.UsedBytesAST); + MT.child("header_includer_cache").addUsage(HeaderIncluders->getUsedBytes()); } } } // namespace clangd diff --git a/clang-tools-extra/clangd/TUScheduler.h b/clang-tools-extra/clangd/TUScheduler.h index 2c381923a37e4252234575b61e7d665b949324d2..8df2c019053e0a292752577be722e21aada6bce9 100644 --- a/clang-tools-extra/clangd/TUScheduler.h +++ b/clang-tools-extra/clangd/TUScheduler.h @@ -235,9 +235,6 @@ public: /// if requested with WantDiags::Auto or WantDiags::Yes. void remove(PathRef File); - /// Returns a snapshot of all file buffer contents, per last update(). - llvm::StringMap getAllFileContents() const; - /// Schedule an async task with no dependencies. /// Path may be empty (it is used only to set the Context). void run(llvm::StringRef Name, llvm::StringRef Path, @@ -310,6 +307,8 @@ public: /// Responsible for retaining and rebuilding idle ASTs. An implementation is /// an LRU cache. class ASTCache; + /// Tracks headers included by open files, to get known-good compile commands. + class HeaderIncluderCache; // The file being built/processed in the current thread. This is a hack in // order to get the file name into the index implementations. Do not depend on @@ -332,6 +331,7 @@ private: Semaphore QuickRunBarrier; llvm::StringMap> Files; std::unique_ptr IdleASTs; + std::unique_ptr HeaderIncluders; // None when running tasks synchronously and non-None when running tasks // asynchronously. llvm::Optional PreambleTasks; diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp index ff3b8d73b64ed97cd301b38587918138d225ef77..1f821f8edd1e06842a6742e7865e433cdcf8dfae 100644 --- a/clang-tools-extra/clangd/XRefs.cpp +++ b/clang-tools-extra/clangd/XRefs.cpp @@ -882,8 +882,8 @@ public: }; ReferenceFinder(const ParsedAST &AST, - const llvm::DenseSet &TargetIDs) - : AST(AST), TargetIDs(TargetIDs) {} + const llvm::DenseSet &TargetIDs, bool PerToken) + : PerToken(PerToken), AST(AST), TargetIDs(TargetIDs) {} std::vector take() && { llvm::sort(References, [](const Reference &L, const Reference &R) { @@ -915,21 +915,43 @@ public: if (!TargetIDs.contains(ID)) return true; const auto &TB = AST.getTokens(); - Loc = SM.getFileLoc(Loc); - if (const auto *Tok = TB.spelledTokenAt(Loc)) - References.push_back({*Tok, Roles, ID}); + + llvm::SmallVector Locs; + if (PerToken) { + // Check whether this is one of the few constructs where the reference + // can be split over several tokens. + if (auto *OME = llvm::dyn_cast_or_null(ASTNode.OrigE)) { + OME->getSelectorLocs(Locs); + } else if (auto *OMD = + llvm::dyn_cast_or_null(ASTNode.OrigD)) { + OMD->getSelectorLocs(Locs); + } + // Sanity check: we expect the *first* token to match the reported loc. + // Otherwise, maybe it was e.g. some other kind of reference to a Decl. + if (!Locs.empty() && Locs.front() != Loc) + Locs.clear(); // First token doesn't match, assume our guess was wrong. + } + if (Locs.empty()) + Locs.push_back(Loc); + + for (SourceLocation L : Locs) { + L = SM.getFileLoc(L); + if (const auto *Tok = TB.spelledTokenAt(L)) + References.push_back({*Tok, Roles, ID}); + } return true; } private: + bool PerToken; // If true, report 3 references for split ObjC selector names. std::vector References; const ParsedAST &AST; const llvm::DenseSet &TargetIDs; }; std::vector -findRefs(const llvm::DenseSet &IDs, ParsedAST &AST) { - ReferenceFinder RefFinder(AST, IDs); +findRefs(const llvm::DenseSet &IDs, ParsedAST &AST, bool PerToken) { + ReferenceFinder RefFinder(AST, IDs, PerToken); index::IndexingOptions IndexOpts; IndexOpts.SystemSymbolFilter = index::IndexingOptions::SystemSymbolFilterKind::All; @@ -1224,7 +1246,7 @@ std::vector findDocumentHighlights(ParsedAST &AST, for (const NamedDecl *ND : Decls) if (auto ID = getSymbolID(ND)) Targets.insert(ID); - for (const auto &Ref : findRefs(Targets, AST)) + for (const auto &Ref : findRefs(Targets, AST, /*PerToken=*/true)) Result.push_back(toHighlight(Ref, SM)); return true; } @@ -1376,7 +1398,7 @@ ReferencesResult findReferences(ParsedAST &AST, Position Pos, uint32_t Limit, } // We traverse the AST to find references in the main file. - auto MainFileRefs = findRefs(Targets, AST); + auto MainFileRefs = findRefs(Targets, AST, /*PerToken=*/false); // We may get multiple refs with the same location and different Roles, as // cross-reference is only interested in locations, we deduplicate them // by the location to avoid emitting duplicated locations. diff --git a/clang-tools-extra/clangd/benchmarks/CompletionModel/DecisionForestBenchmark.cpp b/clang-tools-extra/clangd/benchmarks/CompletionModel/DecisionForestBenchmark.cpp index 69ce65e08b77220f72bb5382e9213632426fd3e8..47d4a76c4dd8568b34a82333e67ec651dfa626d8 100644 --- a/clang-tools-extra/clangd/benchmarks/CompletionModel/DecisionForestBenchmark.cpp +++ b/clang-tools-extra/clangd/benchmarks/CompletionModel/DecisionForestBenchmark.cpp @@ -21,7 +21,7 @@ namespace clang { namespace clangd { namespace { std::vector generateRandomDataset(int NumExamples) { - auto FlipCoin = [&](float Probability) { + auto FlipCoin = [&](double Probability) { return rand() % 1000 <= Probability * 1000; }; auto RandInt = [&](int Max) { return rand() % Max; }; @@ -38,25 +38,24 @@ std::vector generateRandomDataset(int NumExamples) { E.setIsImplementationDetail(FlipCoin(0.3)); // Boolean. E.setNumReferences(RandInt(10000)); // Can be large integer. E.setSymbolCategory(RandInt(10)); // 10 Symbol Category. - + E.setNumNameInContext(RandInt(20)); // 0 to ContextWords->size(). + E.setFractionNameInContext(RandFloat(1.0)); // Float in range [0,1]. E.setIsNameInContext(FlipCoin(0.5)); // Boolean. - E.setIsForbidden(FlipCoin(0.1)); // Boolean. E.setIsInBaseClass(FlipCoin(0.3)); // Boolean. - E.setFileProximityDistance( + E.setFileProximityDistanceCost( FlipCoin(0.1) ? 999999 // Sometimes file distance is not available. : RandInt(20)); E.setSemaFileProximityScore(RandFloat(1)); // Float in range [0,1]. - E.setSymbolScopeDistance( + E.setSymbolScopeDistanceCost( FlipCoin(0.1) ? 999999 // Sometimes scope distance is not available. : RandInt(20)); E.setSemaSaysInScope(FlipCoin(0.5)); // Boolean. E.setScope(RandInt(4)); // 4 Scopes. - E.setContextKind(RandInt(32)); // 32 Context kinds. + E.setContextKind(RandInt(36)); // 36 Context kinds. E.setIsInstanceMember(FlipCoin(0.5)); // Boolean. E.setHadContextType(FlipCoin(0.6)); // Boolean. E.setHadSymbolType(FlipCoin(0.6)); // Boolean. E.setTypeMatchesPreferred(FlipCoin(0.5)); // Boolean. - E.setFilterLength(RandInt(15)); Examples.push_back(E); } return Examples; diff --git a/clang-tools-extra/clangd/benchmarks/IndexBenchmark.cpp b/clang-tools-extra/clangd/benchmarks/IndexBenchmark.cpp index 237655dcc9bc4d32a9d8e580035a497d4056de37..deeb07b4cb648d8b7de6adf0f1828ba38f6c59d3 100644 --- a/clang-tools-extra/clangd/benchmarks/IndexBenchmark.cpp +++ b/clang-tools-extra/clangd/benchmarks/IndexBenchmark.cpp @@ -13,8 +13,6 @@ #include "llvm/ADT/StringRef.h" #include "llvm/Support/Path.h" #include "llvm/Support/Regex.h" -#include -#include #include const char *IndexFilename; @@ -34,9 +32,15 @@ std::unique_ptr buildDex() { // Reads JSON array of serialized FuzzyFindRequest's from user-provided file. std::vector extractQueriesFromLogs() { - std::ifstream InputStream(RequestsFilename); - std::string Log((std::istreambuf_iterator(InputStream)), - std::istreambuf_iterator()); + + auto Buffer = llvm::MemoryBuffer::getFile(RequestsFilename); + if (!Buffer) { + llvm::errs() << "Error cannot open JSON request file:" << RequestsFilename + << ": " << Buffer.getError().message() << "\n"; + exit(1); + } + + StringRef Log = Buffer.get()->getBuffer(); std::vector Requests; auto JSONArray = llvm::json::parse(Log); diff --git a/clang-tools-extra/clangd/fuzzer/clangd-fuzzer.cpp b/clang-tools-extra/clangd/fuzzer/clangd-fuzzer.cpp index 41c603b5fd21a6fea130f3fa2e452f64e4a7a7d6..1c1ac5bf0c77b86c6fc40a2cedd3830460ec30da 100644 --- a/clang-tools-extra/clangd/fuzzer/clangd-fuzzer.cpp +++ b/clang-tools-extra/clangd/fuzzer/clangd-fuzzer.cpp @@ -16,7 +16,6 @@ #include "ClangdServer.h" #include "support/ThreadsafeFS.h" #include -#include using namespace clang::clangd; diff --git a/clang-tools-extra/clangd/index/Background.cpp b/clang-tools-extra/clangd/index/Background.cpp index f97f13d8dabe57a28c643dc30587d29927e83a87..ddfe962d31890c4bec1c501e5b973d0232757067 100644 --- a/clang-tools-extra/clangd/index/Background.cpp +++ b/clang-tools-extra/clangd/index/Background.cpp @@ -243,7 +243,7 @@ void BackgroundIndex::update( // this thread sees the older version but finishes later. This should be // rare in practice. IndexedSymbols.update( - Path, std::make_unique(std::move(*IF->Symbols)), + Uri, std::make_unique(std::move(*IF->Symbols)), std::make_unique(std::move(*IF->Refs)), std::make_unique(std::move(*IF->Relations)), Path == MainFile); @@ -390,8 +390,9 @@ BackgroundIndex::loadProject(std::vector MainFiles) { SV.HadErrors = LS.HadErrors; ++LoadedShards; - IndexedSymbols.update(LS.AbsolutePath, std::move(SS), std::move(RS), - std::move(RelS), LS.CountReferences); + IndexedSymbols.update(URI::create(LS.AbsolutePath).toString(), + std::move(SS), std::move(RS), std::move(RelS), + LS.CountReferences); } } Rebuilder.loadedShard(LoadedShards); diff --git a/clang-tools-extra/clangd/index/FileIndex.cpp b/clang-tools-extra/clangd/index/FileIndex.cpp index 528630f9232a238befd40b8e72f0f63b836e4410..b91c66b8877030632d1902e7716c840b5c625bb1 100644 --- a/clang-tools-extra/clangd/index/FileIndex.cpp +++ b/clang-tools-extra/clangd/index/FileIndex.cpp @@ -463,7 +463,8 @@ void FileIndex::updatePreamble(PathRef Path, llvm::StringRef Version, void FileIndex::updateMain(PathRef Path, ParsedAST &AST) { auto Contents = indexMainDecls(AST); MainFileSymbols.update( - Path, std::make_unique(std::move(std::get<0>(Contents))), + URI::create(Path).toString(), + std::make_unique(std::move(std::get<0>(Contents))), std::make_unique(std::move(std::get<1>(Contents))), std::make_unique(std::move(std::get<2>(Contents))), /*CountReferences=*/true); diff --git a/clang-tools-extra/clangd/index/MemIndex.cpp b/clang-tools-extra/clangd/index/MemIndex.cpp index e2a8eb7f8e3f2d68546713eb8ffe691a4cc517f1..9dc8e0aec9444470f08f5a93ffff3414768ba0fc 100644 --- a/clang-tools-extra/clangd/index/MemIndex.cpp +++ b/clang-tools-extra/clangd/index/MemIndex.cpp @@ -112,14 +112,7 @@ void MemIndex::relations( llvm::unique_function MemIndex::indexedFiles() const { return [this](llvm::StringRef FileURI) { - if (Files.empty()) - return IndexContents::None; - auto Path = URI::resolve(FileURI, Files.begin()->first()); - if (!Path) { - vlog("Failed to resolve the URI {0} : {1}", FileURI, Path.takeError()); - return IndexContents::None; - } - return Files.contains(*Path) ? IdxContents : IndexContents::None; + return Files.contains(FileURI) ? IdxContents : IndexContents::None; }; } diff --git a/clang-tools-extra/clangd/index/SymbolCollector.cpp b/clang-tools-extra/clangd/index/SymbolCollector.cpp index b1363c1f9cef518fbc5782368ece281453618f7a..e330c9fe402db2a10012834d5a42e37190e26cce 100644 --- a/clang-tools-extra/clangd/index/SymbolCollector.cpp +++ b/clang-tools-extra/clangd/index/SymbolCollector.cpp @@ -432,7 +432,8 @@ bool SymbolCollector::handleMacroOccurrence(const IdentifierInfo *Name, const auto &SM = PP->getSourceManager(); auto DefLoc = MI->getDefinitionLoc(); // Also avoid storing predefined macros like __DBL_MIN__. - if (SM.isWrittenInBuiltinFile(DefLoc)) + if (SM.isWrittenInBuiltinFile(DefLoc) || + Name->getName() == "__GCC_HAVE_DWARF2_CFI_ASM") return true; auto ID = getSymbolID(Name->getName(), MI, SM); diff --git a/clang-tools-extra/clangd/index/dex/Dex.cpp b/clang-tools-extra/clangd/index/dex/Dex.cpp index a6a8f23cab4c9b57099fc8ee2e4718dfd1b8da6b..7ebd5f685685ec0022b18075bdb2b0cfef613926 100644 --- a/clang-tools-extra/clangd/index/dex/Dex.cpp +++ b/clang-tools-extra/clangd/index/dex/Dex.cpp @@ -316,14 +316,7 @@ void Dex::relations( llvm::unique_function Dex::indexedFiles() const { return [this](llvm::StringRef FileURI) { - if (Files.empty()) - return IndexContents::None; - auto Path = URI::resolve(FileURI, Files.begin()->first()); - if (!Path) { - vlog("Failed to resolve the URI {0} : {1}", FileURI, Path.takeError()); - return IndexContents::None; - } - return Files.contains(*Path) ? IdxContents : IndexContents::None; + return Files.contains(FileURI) ? IdxContents : IndexContents::None; }; } diff --git a/clang-tools-extra/clangd/index/remote/server/Server.cpp b/clang-tools-extra/clangd/index/remote/server/Server.cpp index 20b140e00787d55cf86140af78c9f05ae3e3b9fc..3de2c38f7c08feea3a902ea73c8e8e662cb0e350 100644 --- a/clang-tools-extra/clangd/index/remote/server/Server.cpp +++ b/clang-tools-extra/clangd/index/remote/server/Server.cpp @@ -80,6 +80,11 @@ llvm::cl::opt ServerAddress( "server-address", llvm::cl::init("0.0.0.0:50051"), llvm::cl::desc("Address of the invoked server. Defaults to 0.0.0.0:50051")); +llvm::cl::opt IdleTimeoutSeconds( + "idle-timeout", llvm::cl::init(8 * 60), + llvm::cl::desc("Maximum time a channel may stay idle until server closes " + "the connection, in seconds. Defaults to 480.")); + static Key CurrentRequest; class RemoteIndexServer final : public v1::SymbolIndex::Service { @@ -311,6 +316,8 @@ void runServerAndWait(clangd::SymbolIndex &Index, llvm::StringRef ServerAddress, grpc::ServerBuilder Builder; Builder.AddListeningPort(ServerAddress.str(), grpc::InsecureServerCredentials()); + Builder.AddChannelArgument(GRPC_ARG_MAX_CONNECTION_IDLE_MS, + IdleTimeoutSeconds * 1000); Builder.RegisterService(&Service); std::unique_ptr Server(Builder.BuildAndStart()); log("Server listening on {0}", ServerAddress); diff --git a/clang-tools-extra/clangd/quality/CompletionModelCodegen.py b/clang-tools-extra/clangd/quality/CompletionModelCodegen.py index f27e8f6a28a3adb8a3363469630d7fbcec47c190..4f2356fceacb00197a4da5f32e9b7bf29c34afb3 100644 --- a/clang-tools-extra/clangd/quality/CompletionModelCodegen.py +++ b/clang-tools-extra/clangd/quality/CompletionModelCodegen.py @@ -131,18 +131,19 @@ def gen_header_code(features_json, cpp_class, filename): feature, feature)) elif f["kind"] == "ENUM": setters.append( - "void set%s(unsigned V) { %s = 1 << V; }" % (feature, feature)) + "void set%s(unsigned V) { %s = 1LL << V; }" % (feature, feature)) else: raise ValueError("Unhandled feature type.", f["kind"]) # Class members represent all the features of the Example. class_members = [ - "uint32_t %s = 0;" % f['name'] + "uint%d_t %s = 0;" + % (64 if f["kind"] == "ENUM" else 32, f['name']) for f in features_json ] getters = [ - "LLVM_ATTRIBUTE_ALWAYS_INLINE uint32_t get%s() const { return %s; }" - % (f['name'], f['name']) + "LLVM_ATTRIBUTE_ALWAYS_INLINE uint%d_t get%s() const { return %s; }" + % (64 if f["kind"] == "ENUM" else 32, f['name'], f['name']) for f in features_json ] nline = "\n " @@ -245,7 +246,7 @@ def gen_cpp_code(forest_json, features_json, filename, cpp_class): %s -#define BIT(X) (1 << X) +#define BIT(X) (1LL << X) %s diff --git a/clang-tools-extra/clangd/refactor/Rename.cpp b/clang-tools-extra/clangd/refactor/Rename.cpp index cc6830d0ab37b5afdadfad36b9876748eddd6693..853fc57bb906e569bd696e5d82cb4dd9832f5b46 100644 --- a/clang-tools-extra/clangd/refactor/Rename.cpp +++ b/clang-tools-extra/clangd/refactor/Rename.cpp @@ -586,10 +586,10 @@ findOccurrencesOutsideFile(const NamedDecl &RenameDecl, // index (background index) is relatively stale. We choose the dirty buffers // as the file content we rename on, and fallback to file content on disk if // there is no dirty buffer. -llvm::Expected renameOutsideFile( - const NamedDecl &RenameDecl, llvm::StringRef MainFilePath, - llvm::StringRef NewName, const SymbolIndex &Index, size_t MaxLimitFiles, - llvm::function_ref(PathRef)> GetFileContent) { +llvm::Expected +renameOutsideFile(const NamedDecl &RenameDecl, llvm::StringRef MainFilePath, + llvm::StringRef NewName, const SymbolIndex &Index, + size_t MaxLimitFiles, llvm::vfs::FileSystem &FS) { trace::Span Tracer("RenameOutsideFile"); auto AffectedFiles = findOccurrencesOutsideFile(RenameDecl, MainFilePath, Index, MaxLimitFiles); @@ -599,13 +599,16 @@ llvm::Expected renameOutsideFile( for (auto &FileAndOccurrences : *AffectedFiles) { llvm::StringRef FilePath = FileAndOccurrences.first(); - auto AffectedFileCode = GetFileContent(FilePath); - if (!AffectedFileCode) { - elog("Fail to read file content: {0}", AffectedFileCode.takeError()); + auto ExpBuffer = FS.getBufferForFile(FilePath); + if (!ExpBuffer) { + elog("Fail to read file content: Fail to open file {0}: {1}", FilePath, + ExpBuffer.getError().message()); continue; } + + auto AffectedFileCode = (*ExpBuffer)->getBuffer(); auto RenameRanges = - adjustRenameRanges(*AffectedFileCode, RenameDecl.getNameAsString(), + adjustRenameRanges(AffectedFileCode, RenameDecl.getNameAsString(), std::move(FileAndOccurrences.second), RenameDecl.getASTContext().getLangOpts()); if (!RenameRanges) { @@ -617,7 +620,7 @@ llvm::Expected renameOutsideFile( FilePath); } auto RenameEdit = - buildRenameEdit(FilePath, *AffectedFileCode, *RenameRanges, NewName); + buildRenameEdit(FilePath, AffectedFileCode, *RenameRanges, NewName); if (!RenameEdit) return error("failed to rename in file {0}: {1}", FilePath, RenameEdit.takeError()); @@ -668,28 +671,13 @@ void findNearMiss( } // namespace llvm::Expected rename(const RenameInputs &RInputs) { + assert(!RInputs.Index == !RInputs.FS && + "Index and FS must either both be specified or both null."); trace::Span Tracer("Rename flow"); const auto &Opts = RInputs.Opts; ParsedAST &AST = RInputs.AST; const SourceManager &SM = AST.getSourceManager(); llvm::StringRef MainFileCode = SM.getBufferData(SM.getMainFileID()); - auto GetFileContent = [&RInputs, - &SM](PathRef AbsPath) -> llvm::Expected { - llvm::Optional DirtyBuffer; - if (RInputs.GetDirtyBuffer && - (DirtyBuffer = RInputs.GetDirtyBuffer(AbsPath))) - return std::move(*DirtyBuffer); - - auto Content = - SM.getFileManager().getVirtualFileSystem().getBufferForFile(AbsPath); - if (!Content) - return error("Fail to open file {0}: {1}", AbsPath, - Content.getError().message()); - if (!*Content) - return error("Got no buffer for file {0}", AbsPath); - - return (*Content)->getBuffer().str(); - }; // Try to find the tokens adjacent to the cursor position. auto Loc = sourceLocationInMainFile(SM, RInputs.Pos); if (!Loc) @@ -765,7 +753,7 @@ llvm::Expected rename(const RenameInputs &RInputs) { RenameDecl, RInputs.MainFilePath, RInputs.NewName, *RInputs.Index, Opts.LimitFiles == 0 ? std::numeric_limits::max() : Opts.LimitFiles, - GetFileContent); + *RInputs.FS); if (!OtherFilesEdits) return OtherFilesEdits.takeError(); Result.GlobalChanges = *OtherFilesEdits; diff --git a/clang-tools-extra/clangd/refactor/Rename.h b/clang-tools-extra/clangd/refactor/Rename.h index 7eca4610eacacdf05c53321b7aa1e00d1798c84f..03ae4f7f1563ed1a5814d1445793dd650d92c2af 100644 --- a/clang-tools-extra/clangd/refactor/Rename.h +++ b/clang-tools-extra/clangd/refactor/Rename.h @@ -21,11 +21,6 @@ namespace clangd { class ParsedAST; class SymbolIndex; -/// Gets dirty buffer for a given file \p AbsPath. -/// Returns None if there is no dirty buffer for the given file. -using DirtyBufferGetter = - llvm::function_ref(PathRef AbsPath)>; - struct RenameOptions { /// The maximum number of affected files (0 means no limit), only meaningful /// when AllowCrossFile = true. @@ -42,14 +37,14 @@ struct RenameInputs { ParsedAST &AST; llvm::StringRef MainFilePath; + // The filesystem to query when performing cross file renames. + // If this is set, Index must also be set, likewise if this is nullptr, Index + // must also be nullptr. + llvm::IntrusiveRefCntPtr FS = nullptr; + const SymbolIndex *Index = nullptr; RenameOptions Opts = {}; - // When set, used by the rename to get file content for all rename-related - // files. - // If there is no corresponding dirty buffer, we will use the file content - // from disk. - DirtyBufferGetter GetDirtyBuffer = nullptr; }; struct RenameResult { diff --git a/clang-tools-extra/clangd/test/crash-non-added-files.test b/clang-tools-extra/clangd/test/crash-non-added-files.test index d86f7d26d870fcec164f05522c20cf0a148d16d6..55736bb715b044540eeb52787ee85c3d58bfcafe 100644 --- a/clang-tools-extra/clangd/test/crash-non-added-files.test +++ b/clang-tools-extra/clangd/test/crash-non-added-files.test @@ -4,28 +4,28 @@ {"jsonrpc":"2.0","id":2,"method":"textDocument/codeAction","params":{"textDocument":{"uri":"test:///foo.c"},"range":{"start":{"line":104,"character":13},"end":{"line":0,"character":35}},"context":{"diagnostics":[{"range":{"start": {"line": 0, "character": 32}, "end": {"line": 0, "character": 37}},"severity":2,"message":"using the result of an assignment as a condition without parentheses"},{"range":{"start": {"line": 0, "character": 34}, "end": {"line": 0, "character": 35}},"severity":3,"message":"place parentheses around the assignment to silence this warning"},{"range":{"start": {"line": 0, "character": 34}, "end": {"line": 0, "character": 35}},"severity":3,"message":"use '==' to turn this assignment into an equality comparison"}]}}} # CHECK: "error": { # CHECK-NEXT: "code": -32602 -# CHECK-NEXT: "message": "onCodeAction called for non-added file" +# CHECK-NEXT: "message": "trying to get AST for non-added document" # CHECK-NEXT: }, # CHECK-NEXT: "id": 2, --- {"jsonrpc":"2.0","id":3,"method":"textDocument/rangeFormatting","params":{"textDocument":{"uri":"test:///foo.c"},"range":{"start":{"line":1,"character":4},"end":{"line":1,"character":12}},"options":{"tabSize":4,"insertSpaces":true}}} # CHECK: "error": { # CHECK-NEXT: "code": -32602 -# CHECK-NEXT: "message": "onDocumentRangeFormatting called for non-added file" +# CHECK-NEXT: "message": "trying to format non-added document" # CHECK-NEXT: }, # CHECK-NEXT: "id": 3, --- {"jsonrpc":"2.0","id":4,"method":"textDocument/formatting","params":{"textDocument":{"uri":"test:///foo.c"},"options":{"tabSize":4,"insertSpaces":true}}} # CHECK: "error": { # CHECK-NEXT: "code": -32602 -# CHECK-NEXT: "message": "onDocumentFormatting called for non-added file" +# CHECK-NEXT: "message": "trying to format non-added document" # CHECK-NEXT: }, # CHECK-NEXT: "id": 4, --- {"jsonrpc":"2.0","id":5,"method":"textDocument/onTypeFormatting","params":{"textDocument":{"uri":"test:///foo.c"},"position":{"line":3,"character":1},"ch":"}","options":{"tabSize":4,"insertSpaces":true}}} # CHECK: "error": { # CHECK-NEXT: "code": -32602 -# CHECK-NEXT: "message": "onDocumentOnTypeFormatting called for non-added file" +# CHECK-NEXT: "message": "trying to format non-added document" # CHECK-NEXT: }, # CHECK-NEXT: "id": 5, --- diff --git a/clang-tools-extra/clangd/test/memory_tree.test b/clang-tools-extra/clangd/test/memory_tree.test index c0a6aaf266ab51dcd14e666d3866b78a46bfed99..207f5abd55e12d66b795b20be08dab17080a1d15 100644 --- a/clang-tools-extra/clangd/test/memory_tree.test +++ b/clang-tools-extra/clangd/test/memory_tree.test @@ -23,7 +23,9 @@ # CHECK-NEXT: "_total": {{[0-9]+}} # CHECK-NEXT: }, # CHECK-NEXT: "slabs": { -# CHECK-NEXT: "{{.*}}main.cpp": { +# CHECK-NEXT: "_self": {{[0-9]+}}, +# CHECK-NEXT: "_total": {{[0-9]+}}, +# CHECK-NEXT: "test:///main.cpp": { # CHECK-NEXT: "_self": {{[0-9]+}}, # CHECK-NEXT: "_total": {{[0-9]+}}, # CHECK-NEXT: "references": { @@ -38,9 +40,7 @@ # CHECK-NEXT: "_self": {{[0-9]+}}, # CHECK-NEXT: "_total": {{[0-9]+}} # CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: "_self": {{[0-9]+}}, -# CHECK-NEXT: "_total": {{[0-9]+}} +# CHECK-NEXT: } # CHECK-NEXT: } # CHECK-NEXT: }, # CHECK-NEXT: "preamble": { @@ -70,7 +70,11 @@ # CHECK-NEXT: } # CHECK-NEXT: }, # CHECK-NEXT: "_self": {{[0-9]+}}, -# CHECK-NEXT: "_total": {{[0-9]+}} +# CHECK-NEXT: "_total": {{[0-9]+}}, +# CHECK-NEXT: "header_includer_cache": { +# CHECK-NEXT: "_self": {{[0-9]+}}, +# CHECK-NEXT: "_total": {{[0-9]+}} +# CHECK-NEXT: } # CHECK-NEXT: } # CHECK-NEXT: } # CHECK-NEXT: } diff --git a/clang-tools-extra/clangd/test/semantic-tokens-refresh.test b/clang-tools-extra/clangd/test/semantic-tokens-refresh.test new file mode 100644 index 0000000000000000000000000000000000000000..be2802ce7568d0e51e7b46a40d10738e7669a9ce --- /dev/null +++ b/clang-tools-extra/clangd/test/semantic-tokens-refresh.test @@ -0,0 +1,42 @@ +# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s +{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"capabilities":{ + "workspace":{"semanticTokens":{"refreshSupport":true}} +}}} +--- +{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{ + "uri": "test:///foo.cpp", + "languageId": "cpp", + "text": "int x = 2;" +}}} +# Expect a request after initial preamble build. +# CHECK: "method": "workspace/semanticTokens/refresh", +# CHECK-NEXT: "params": null +# CHECK-NEXT: } +--- +# Reply with success. +{"jsonrpc":"2.0","id":0} +--- +# Preamble stays the same, no refresh requests. +{"jsonrpc":"2.0","method":"textDocument/didChange","params":{ + "textDocument": {"uri":"test:///foo.cpp","version":2}, + "contentChanges":[{"text":"int x = 2;\nint y = 3;"}] +}} +# CHECK-NOT: "method": "workspace/semanticTokens/refresh" +--- +# Preamble changes +{"jsonrpc":"2.0","method":"textDocument/didChange","params":{ + "textDocument": {"uri":"test:///foo.cpp","version":2}, + "contentChanges":[{"text":"#define FOO"}] +}} +# Expect a request after initial preamble build. +# CHECK: "method": "workspace/semanticTokens/refresh", +# CHECK-NEXT: "params": null +# CHECK-NEXT: } +--- +# Reply with error, to make sure there are no crashes. +{"jsonrpc":"2.0","id":1,"error":{"code": 0, "message": "msg"}} +--- +{"jsonrpc":"2.0","id":3,"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 51280b73afac6d302a0de4dfe81c1eb06217f496..3afcd5f0833350e1edd381a03c0eb1298246b02a 100644 --- a/clang-tools-extra/clangd/tool/ClangdMain.cpp +++ b/clang-tools-extra/clangd/tool/ClangdMain.cpp @@ -39,7 +39,6 @@ #include "llvm/Support/raw_ostream.h" #include #include -#include #include #include #include diff --git a/clang-tools-extra/clangd/unittests/ClangdLSPServerTests.cpp b/clang-tools-extra/clangd/unittests/ClangdLSPServerTests.cpp index 695ed89ae7f44798fd64aacba7d5d5e0a2131d2b..f596f94bd6cd8af486dafa42630e87f3c40bdcaf 100644 --- a/clang-tools-extra/clangd/unittests/ClangdLSPServerTests.cpp +++ b/clang-tools-extra/clangd/unittests/ClangdLSPServerTests.cpp @@ -35,14 +35,14 @@ MATCHER_P(DiagMessage, M, "") { return false; } -class LSPTest : public ::testing::Test, private clangd::Logger { +class LSPTest : public ::testing::Test { protected: - LSPTest() : LogSession(*this) { + LSPTest() : LogSession(L) { ClangdServer::Options &Base = Opts; Base = ClangdServer::optsForTest(); // This is needed to we can test index-based operations like call hierarchy. Base.BuildDynamicSymbolIndex = true; - Base.Modules = &Modules; + Base.FeatureModules = &FeatureModules; } LSPClient &start() { @@ -70,29 +70,32 @@ protected: MockFS FS; ClangdLSPServer::Options Opts; - ModuleSet Modules; + FeatureModuleSet FeatureModules; private: - // Color logs so we can distinguish them from test output. - void log(Level L, const char *Fmt, - const llvm::formatv_object_base &Message) override { - raw_ostream::Colors Color; - switch (L) { - case Level::Verbose: - Color = raw_ostream::BLUE; - break; - case Level::Error: - Color = raw_ostream::RED; - break; - default: - Color = raw_ostream::YELLOW; - break; + class Logger : public clang::clangd::Logger { + // Color logs so we can distinguish them from test output. + void log(Level L, const char *Fmt, + const llvm::formatv_object_base &Message) override { + raw_ostream::Colors Color; + switch (L) { + case Level::Verbose: + Color = raw_ostream::BLUE; + break; + case Level::Error: + Color = raw_ostream::RED; + break; + default: + Color = raw_ostream::YELLOW; + break; + } + std::lock_guard Lock(LogMu); + (llvm::outs().changeColor(Color) << Message << "\n").resetColor(); } - std::lock_guard Lock(LogMu); - (llvm::outs().changeColor(Color) << Message << "\n").resetColor(); - } - std::mutex LogMu; + std::mutex LogMu; + }; + Logger L; LoggingSession LogSession; llvm::Optional Server; llvm::Optional ServerThread; @@ -227,7 +230,7 @@ CompileFlags: } TEST_F(LSPTest, ModulesTest) { - class MathModule final : public Module { + class MathModule final : public FeatureModule { OutgoingNotification Changed; void initializeLSP(LSPBinder &Bind, const llvm::json::Object &ClientCaps, llvm::json::Object &ServerCaps) override { @@ -248,7 +251,7 @@ TEST_F(LSPTest, ModulesTest) { [Reply(std::move(Reply)), Value(Value)]() mutable { Reply(Value); }); } }; - Modules.add(std::make_unique()); + FeatureModules.add(std::make_unique()); auto &Client = start(); Client.notify("add", 2); @@ -266,10 +269,10 @@ capture(llvm::Optional> &Out) { return [&Out](llvm::Expected V) { Out.emplace(std::move(V)); }; } -TEST_F(LSPTest, ModulesThreadingTest) { - // A module that does its work on a background thread, and so exercises the - // block/shutdown protocol. - class AsyncCounter final : public Module { +TEST_F(LSPTest, FeatureModulesThreadingTest) { + // A feature module that does its work on a background thread, and so + // exercises the block/shutdown protocol. + class AsyncCounter final : public FeatureModule { bool ShouldStop = false; int State = 0; std::deque> Queue; // null = increment, non-null = read. @@ -347,19 +350,19 @@ TEST_F(LSPTest, ModulesThreadingTest) { } }; - Modules.add(std::make_unique()); + FeatureModules.add(std::make_unique()); auto &Client = start(); Client.notify("increment", nullptr); Client.notify("increment", nullptr); Client.notify("increment", nullptr); EXPECT_THAT_EXPECTED(Client.call("sync", nullptr).take(), Succeeded()); - EXPECT_EQ(3, Modules.get()->getSync()); + EXPECT_EQ(3, FeatureModules.get()->getSync()); // Throw some work on the queue to make sure shutdown blocks on it. Client.notify("increment", nullptr); Client.notify("increment", nullptr); Client.notify("increment", nullptr); - // And immediately shut down. Module destructor verifies that we blocked. + // And immediately shut down. FeatureModule destructor verifies we blocked. } } // namespace diff --git a/clang-tools-extra/clangd/unittests/ClangdTests.cpp b/clang-tools-extra/clangd/unittests/ClangdTests.cpp index f10a994fca5c1394848fd6e2ac4968be8a77da8b..15320e8bd8e8009c17f46b40fb4f93008e6f5ef7 100644 --- a/clang-tools-extra/clangd/unittests/ClangdTests.cpp +++ b/clang-tools-extra/clangd/unittests/ClangdTests.cpp @@ -945,7 +945,7 @@ void f() {} FS.Files[Path] = Code; runAddDocument(Server, Path, Code); - auto Replaces = runFormatFile(Server, Path, Code); + auto Replaces = runFormatFile(Server, Path, /*Rng=*/llvm::None); EXPECT_TRUE(static_cast(Replaces)); auto Changed = tooling::applyAllReplacements(Code, *Replaces); EXPECT_TRUE(static_cast(Changed)); diff --git a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp index b7a40179aa98b71e08bef36e3582cc06381ed922..0ff1e83b7613e313df0b5bcf1fa17bb37b231483 100644 --- a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp +++ b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp @@ -647,13 +647,13 @@ TEST(CompletionTest, ScopedWithFilter) { } TEST(CompletionTest, ReferencesAffectRanking) { - auto Results = completions("int main() { abs^ }", {ns("absl"), func("absb")}); - EXPECT_THAT(Results.Completions, - HasSubsequence(Named("absb"), Named("absl"))); - Results = completions("int main() { abs^ }", - {withReferences(10000, ns("absl")), func("absb")}); - EXPECT_THAT(Results.Completions, - HasSubsequence(Named("absl"), Named("absb"))); + EXPECT_THAT(completions("int main() { abs^ }", {func("absA"), func("absB")}) + .Completions, + HasSubsequence(Named("absA"), Named("absB"))); + EXPECT_THAT(completions("int main() { abs^ }", + {func("absA"), withReferences(1000, func("absB"))}) + .Completions, + HasSubsequence(Named("absB"), Named("absA"))); } TEST(CompletionTest, ContextWords) { diff --git a/clang-tools-extra/clangd/unittests/DexTests.cpp b/clang-tools-extra/clangd/unittests/DexTests.cpp index 8ff319b694dfcfb3a76ccc9749e6c7d317baca57..60d0db081dbb4bc7986641293699a1b33194659c 100644 --- a/clang-tools-extra/clangd/unittests/DexTests.cpp +++ b/clang-tools-extra/clangd/unittests/DexTests.cpp @@ -737,7 +737,7 @@ TEST(DexIndex, IndexedFiles) { RefSlab Refs; auto Size = Symbols.bytes() + Refs.bytes(); auto Data = std::make_pair(std::move(Symbols), std::move(Refs)); - llvm::StringSet<> Files = {testPath("foo.cc"), testPath("bar.cc")}; + llvm::StringSet<> Files = {"unittest:///foo.cc", "unittest:///bar.cc"}; Dex I(std::move(Data.first), std::move(Data.second), RelationSlab(), std::move(Files), IndexContents::All, std::move(Data), Size); auto ContainsFile = I.indexedFiles(); diff --git a/clang-tools-extra/clangd/unittests/DraftStoreTests.cpp b/clang-tools-extra/clangd/unittests/DraftStoreTests.cpp index 5ff9f254b55da4ef58478004966494d884360b4b..5375ea4273af721d5ca238307a73141d063bb861 100644 --- a/clang-tools-extra/clangd/unittests/DraftStoreTests.cpp +++ b/clang-tools-extra/clangd/unittests/DraftStoreTests.cpp @@ -9,6 +9,7 @@ #include "Annotations.h" #include "DraftStore.h" #include "SourceCode.h" +#include "llvm/Support/ScopedPrinter.h" #include "llvm/Testing/Support/Error.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -17,371 +18,26 @@ namespace clang { namespace clangd { namespace { -struct IncrementalTestStep { - llvm::StringRef Src; - llvm::StringRef Contents; -}; - -int rangeLength(llvm::StringRef Code, const Range &Rng) { - llvm::Expected Start = positionToOffset(Code, Rng.start); - llvm::Expected End = positionToOffset(Code, Rng.end); - assert(Start); - assert(End); - return *End - *Start; -} - -/// Send the changes one by one to updateDraft, verify the intermediate results. -void stepByStep(llvm::ArrayRef Steps) { - DraftStore DS; - Annotations InitialSrc(Steps.front().Src); - constexpr llvm::StringLiteral Path("/hello.cpp"); - - // Set the initial content. - EXPECT_EQ(0, DS.addDraft(Path, llvm::None, InitialSrc.code())); - - for (size_t i = 1; i < Steps.size(); i++) { - Annotations SrcBefore(Steps[i - 1].Src); - Annotations SrcAfter(Steps[i].Src); - llvm::StringRef Contents = Steps[i - 1].Contents; - TextDocumentContentChangeEvent Event{ - SrcBefore.range(), - rangeLength(SrcBefore.code(), SrcBefore.range()), - Contents.str(), - }; - - llvm::Expected Result = - DS.updateDraft(Path, llvm::None, {Event}); - ASSERT_TRUE(!!Result); - EXPECT_EQ(Result->Contents, SrcAfter.code()); - EXPECT_EQ(DS.getDraft(Path)->Contents, SrcAfter.code()); - EXPECT_EQ(Result->Version, static_cast(i)); - } -} - -/// Send all the changes at once to updateDraft, check only the final result. -void allAtOnce(llvm::ArrayRef Steps) { - DraftStore DS; - Annotations InitialSrc(Steps.front().Src); - Annotations FinalSrc(Steps.back().Src); - constexpr llvm::StringLiteral Path("/hello.cpp"); - std::vector Changes; - - for (size_t i = 0; i < Steps.size() - 1; i++) { - Annotations Src(Steps[i].Src); - llvm::StringRef Contents = Steps[i].Contents; - - Changes.push_back({ - Src.range(), - rangeLength(Src.code(), Src.range()), - Contents.str(), - }); - } - - // Set the initial content. - EXPECT_EQ(0, DS.addDraft(Path, llvm::None, InitialSrc.code())); - - llvm::Expected Result = - DS.updateDraft(Path, llvm::None, Changes); - - ASSERT_TRUE(!!Result) << llvm::toString(Result.takeError()); - EXPECT_EQ(Result->Contents, FinalSrc.code()); - EXPECT_EQ(DS.getDraft(Path)->Contents, FinalSrc.code()); - EXPECT_EQ(Result->Version, 1); -} - -TEST(DraftStoreIncrementalUpdateTest, Simple) { - // clang-format off - IncrementalTestStep Steps[] = - { - // Replace a range - { -R"cpp(static int -hello[[World]]() -{})cpp", - "Universe" - }, - // Delete a range - { -R"cpp(static int -hello[[Universe]]() -{})cpp", - "" - }, - // Add a range - { -R"cpp(static int -hello[[]]() -{})cpp", - "Monde" - }, - { -R"cpp(static int -helloMonde() -{})cpp", - "" - } - }; - // clang-format on - - stepByStep(Steps); - allAtOnce(Steps); -} - -TEST(DraftStoreIncrementalUpdateTest, MultiLine) { - // clang-format off - IncrementalTestStep Steps[] = - { - // Replace a range - { -R"cpp(static [[int -helloWorld]]() -{})cpp", -R"cpp(char -welcome)cpp" - }, - // Delete a range - { -R"cpp(static char[[ -welcome]]() -{})cpp", - "" - }, - // Add a range - { -R"cpp(static char[[]]() -{})cpp", - R"cpp( -cookies)cpp" - }, - // Replace the whole file - { -R"cpp([[static char -cookies() -{}]])cpp", - R"cpp(#include -)cpp" - }, - // Delete the whole file - { - R"cpp([[#include -]])cpp", - "", - }, - // Add something to an empty file - { - "[[]]", - R"cpp(int main() { -)cpp", - }, - { - R"cpp(int main() { -)cpp", - "" - } - }; - // clang-format on - - stepByStep(Steps); - allAtOnce(Steps); -} - -TEST(DraftStoreIncrementalUpdateTest, WrongRangeLength) { - DraftStore DS; - Path File = "foo.cpp"; - - DS.addDraft(File, llvm::None, "int main() {}\n"); - - TextDocumentContentChangeEvent Change; - Change.range.emplace(); - Change.range->start.line = 0; - Change.range->start.character = 0; - Change.range->end.line = 0; - Change.range->end.character = 2; - Change.rangeLength = 10; - - Expected Result = - DS.updateDraft(File, llvm::None, {Change}); - - EXPECT_TRUE(!Result); - EXPECT_EQ( - toString(Result.takeError()), - "Change's rangeLength (10) doesn't match the computed range length (2)."); -} - -TEST(DraftStoreIncrementalUpdateTest, EndBeforeStart) { - DraftStore DS; - Path File = "foo.cpp"; - - DS.addDraft(File, llvm::None, "int main() {}\n"); - - TextDocumentContentChangeEvent Change; - Change.range.emplace(); - Change.range->start.line = 0; - Change.range->start.character = 5; - Change.range->end.line = 0; - Change.range->end.character = 3; - - auto Result = DS.updateDraft(File, llvm::None, {Change}); - - EXPECT_TRUE(!Result); - EXPECT_EQ(toString(Result.takeError()), - "Range's end position (0:3) is before start position (0:5)"); -} - -TEST(DraftStoreIncrementalUpdateTest, StartCharOutOfRange) { - DraftStore DS; - Path File = "foo.cpp"; - - DS.addDraft(File, llvm::None, "int main() {}\n"); - - TextDocumentContentChangeEvent Change; - Change.range.emplace(); - Change.range->start.line = 0; - Change.range->start.character = 100; - Change.range->end.line = 0; - Change.range->end.character = 100; - Change.text = "foo"; - - auto Result = DS.updateDraft(File, llvm::None, {Change}); - - EXPECT_TRUE(!Result); - EXPECT_EQ(toString(Result.takeError()), - "utf-16 offset 100 is invalid for line 0"); -} - -TEST(DraftStoreIncrementalUpdateTest, EndCharOutOfRange) { - DraftStore DS; - Path File = "foo.cpp"; - - DS.addDraft(File, llvm::None, "int main() {}\n"); - - TextDocumentContentChangeEvent Change; - Change.range.emplace(); - Change.range->start.line = 0; - Change.range->start.character = 0; - Change.range->end.line = 0; - Change.range->end.character = 100; - Change.text = "foo"; - - auto Result = DS.updateDraft(File, llvm::None, {Change}); - - EXPECT_TRUE(!Result); - EXPECT_EQ(toString(Result.takeError()), - "utf-16 offset 100 is invalid for line 0"); -} - -TEST(DraftStoreIncrementalUpdateTest, StartLineOutOfRange) { - DraftStore DS; - Path File = "foo.cpp"; - - DS.addDraft(File, llvm::None, "int main() {}\n"); - - TextDocumentContentChangeEvent Change; - Change.range.emplace(); - Change.range->start.line = 100; - Change.range->start.character = 0; - Change.range->end.line = 100; - Change.range->end.character = 0; - Change.text = "foo"; - - auto Result = DS.updateDraft(File, llvm::None, {Change}); - - EXPECT_TRUE(!Result); - EXPECT_EQ(toString(Result.takeError()), "Line value is out of range (100)"); -} - -TEST(DraftStoreIncrementalUpdateTest, EndLineOutOfRange) { +TEST(DraftStore, Versions) { DraftStore DS; Path File = "foo.cpp"; - DS.addDraft(File, llvm::None, "int main() {}\n"); + EXPECT_EQ("25", DS.addDraft(File, "25", "")); + EXPECT_EQ("25", DS.getDraft(File)->Version); + EXPECT_EQ("", *DS.getDraft(File)->Contents); - TextDocumentContentChangeEvent Change; - Change.range.emplace(); - Change.range->start.line = 0; - Change.range->start.character = 0; - Change.range->end.line = 100; - Change.range->end.character = 0; - Change.text = "foo"; + EXPECT_EQ("26", DS.addDraft(File, "", "x")); + EXPECT_EQ("26", DS.getDraft(File)->Version); + EXPECT_EQ("x", *DS.getDraft(File)->Contents); - auto Result = DS.updateDraft(File, llvm::None, {Change}); - - EXPECT_TRUE(!Result); - EXPECT_EQ(toString(Result.takeError()), "Line value is out of range (100)"); -} - -/// Check that if a valid change is followed by an invalid change, the original -/// version of the document (prior to all changes) is kept. -TEST(DraftStoreIncrementalUpdateTest, InvalidRangeInASequence) { - DraftStore DS; - Path File = "foo.cpp"; - - StringRef OriginalContents = "int main() {}\n"; - EXPECT_EQ(0, DS.addDraft(File, llvm::None, OriginalContents)); - - // The valid change - TextDocumentContentChangeEvent Change1; - Change1.range.emplace(); - Change1.range->start.line = 0; - Change1.range->start.character = 0; - Change1.range->end.line = 0; - Change1.range->end.character = 0; - Change1.text = "Hello "; - - // The invalid change - TextDocumentContentChangeEvent Change2; - Change2.range.emplace(); - Change2.range->start.line = 0; - Change2.range->start.character = 5; - Change2.range->end.line = 0; - Change2.range->end.character = 100; - Change2.text = "something"; - - auto Result = DS.updateDraft(File, llvm::None, {Change1, Change2}); - - EXPECT_TRUE(!Result); - EXPECT_EQ(toString(Result.takeError()), - "utf-16 offset 100 is invalid for line 0"); - - Optional Contents = DS.getDraft(File); - EXPECT_TRUE(Contents); - EXPECT_EQ(Contents->Contents, OriginalContents); - EXPECT_EQ(Contents->Version, 0); -} - -TEST(DraftStore, Version) { - DraftStore DS; - Path File = "foo.cpp"; - - EXPECT_EQ(25, DS.addDraft(File, 25, "")); - EXPECT_EQ(25, DS.getDraft(File)->Version); - - EXPECT_EQ(26, DS.addDraft(File, llvm::None, "")); - EXPECT_EQ(26, DS.getDraft(File)->Version); + EXPECT_EQ("27", DS.addDraft(File, "", "x")) << "no-op change"; + EXPECT_EQ("27", DS.getDraft(File)->Version); + EXPECT_EQ("x", *DS.getDraft(File)->Contents); // We allow versions to go backwards. - EXPECT_EQ(7, DS.addDraft(File, 7, "")); - EXPECT_EQ(7, DS.getDraft(File)->Version); - - // Valid (no-op) change modifies version. - auto Result = DS.updateDraft(File, 10, {}); - EXPECT_TRUE(!!Result); - EXPECT_EQ(10, Result->Version); - EXPECT_EQ(10, DS.getDraft(File)->Version); - - Result = DS.updateDraft(File, llvm::None, {}); - EXPECT_TRUE(!!Result); - EXPECT_EQ(11, Result->Version); - EXPECT_EQ(11, DS.getDraft(File)->Version); - - TextDocumentContentChangeEvent InvalidChange; - InvalidChange.range.emplace(); - InvalidChange.rangeLength = 99; - - Result = DS.updateDraft(File, 15, {InvalidChange}); - EXPECT_FALSE(!!Result); - consumeError(Result.takeError()); - EXPECT_EQ(11, DS.getDraft(File)->Version); + EXPECT_EQ("7", DS.addDraft(File, "7", "y")); + EXPECT_EQ("7", DS.getDraft(File)->Version); + EXPECT_EQ("y", *DS.getDraft(File)->Contents); } } // namespace diff --git a/clang-tools-extra/clangd/unittests/FileIndexTests.cpp b/clang-tools-extra/clangd/unittests/FileIndexTests.cpp index 4ad5c3e183472a22ae7ab7f99bf7c8b391803633..5f1998af3c21f406f3dec48c84aa15c2eb6286cd 100644 --- a/clang-tools-extra/clangd/unittests/FileIndexTests.cpp +++ b/clang-tools-extra/clangd/unittests/FileIndexTests.cpp @@ -352,14 +352,14 @@ TEST(FileIndexTest, Refs) { Test.Code = std::string(MainCode.code()); Test.Filename = "test.cc"; auto AST = Test.build(); - Index.updateMain(Test.Filename, AST); + Index.updateMain(testPath(Test.Filename), AST); // Add test2.cc TestTU Test2; Test2.HeaderCode = HeaderCode; Test2.Code = std::string(MainCode.code()); Test2.Filename = "test2.cc"; AST = Test2.build(); - Index.updateMain(Test2.Filename, AST); + Index.updateMain(testPath(Test2.Filename), AST); EXPECT_THAT(getRefs(Index, Foo.ID), RefsAre({AllOf(RefRange(MainCode.range("foo")), @@ -387,7 +387,7 @@ TEST(FileIndexTest, MacroRefs) { Test.Code = std::string(MainCode.code()); Test.Filename = "test.cc"; auto AST = Test.build(); - Index.updateMain(Test.Filename, AST); + Index.updateMain(testPath(Test.Filename), AST); auto HeaderMacro = findSymbol(Test.headerSymbols(), "HEADER_MACRO"); EXPECT_THAT(getRefs(Index, HeaderMacro.ID), diff --git a/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp b/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp index 7333d451fde15729624012e4224982045dfe896a..30aca71a0570345987e292d8c10b7722bb71f2bc 100644 --- a/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp +++ b/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp @@ -216,7 +216,9 @@ TEST(WorkspaceSymbols, GlobalNamespaceQueries) { AllOf(QName("foo"), WithKind(SymbolKind::Function)), AllOf(QName("ns"), WithKind(SymbolKind::Namespace)))); EXPECT_THAT(getSymbols(TU, ":"), IsEmpty()); - EXPECT_THAT(getSymbols(TU, ""), IsEmpty()); + EXPECT_THAT(getSymbols(TU, ""), + UnorderedElementsAre(QName("foo"), QName("Foo"), QName("Foo::a"), + QName("ns"), QName("ns::foo2"))); } TEST(WorkspaceSymbols, Enums) { @@ -662,7 +664,76 @@ TEST(DocumentSymbols, Enums) { WithDetail("(unnamed)")))))))); } -TEST(DocumentSymbols, FromMacro) { +TEST(DocumentSymbols, Macro) { + struct Test { + const char *Code; + testing::Matcher Matcher; + } Tests[] = { + { + R"cpp( + // Basic macro that generates symbols. + #define DEFINE_FLAG(X) bool FLAGS_##X; bool FLAGS_no##X + DEFINE_FLAG(pretty); + )cpp", + AllOf(WithName("DEFINE_FLAG"), WithDetail("(pretty)"), + Children(WithName("FLAGS_pretty"), WithName("FLAGS_nopretty"))), + }, + { + R"cpp( + // Hierarchy is determined by primary (name) location. + #define ID(X) X + namespace ID(ns) { int ID(y); } + )cpp", + AllOf(WithName("ID"), WithDetail("(ns)"), + Children(AllOf(WithName("ns"), + Children(AllOf(WithName("ID"), WithDetail("(y)"), + Children(WithName("y"))))))), + }, + { + R"cpp( + // More typical example where macro only generates part of a decl. + #define TEST(A, B) class A##_##B { void go(); }; void A##_##B::go() + TEST(DocumentSymbols, Macro) { } + )cpp", + AllOf(WithName("TEST"), WithDetail("(DocumentSymbols, Macro)"), + Children(AllOf(WithName("DocumentSymbols_Macro"), + Children(WithName("go"))), + WithName("DocumentSymbols_Macro::go"))), + }, + { + R"cpp( + // Nested macros. + #define NAMESPACE(NS, BODY) namespace NS { BODY } + NAMESPACE(a, NAMESPACE(b, int x;)) + )cpp", + AllOf( + WithName("NAMESPACE"), WithDetail("(a, NAMESPACE(b, int x;))"), + Children(AllOf( + WithName("a"), + Children(AllOf(WithName("NAMESPACE"), + // FIXME: nested expansions not in TokenBuffer + WithDetail(""), + Children(AllOf(WithName("b"), + Children(WithName("x"))))))))), + }, + { + R"cpp( + // Macro invoked from body is not exposed. + #define INNER(X) int X + #define OUTER(X) INNER(X) + OUTER(foo); + )cpp", + AllOf(WithName("OUTER"), WithDetail("(foo)"), + Children(WithName("foo"))), + }, + }; + for (const Test &T : Tests) { + auto TU = TestTU::withCode(T.Code); + EXPECT_THAT(getSymbols(TU.build()), ElementsAre(T.Matcher)) << T.Code; + } +} + +TEST(DocumentSymbols, RangeFromMacro) { TestTU TU; Annotations Main(R"( #define FF(name) \ @@ -671,9 +742,9 @@ TEST(DocumentSymbols, FromMacro) { $expansion1[[FF]](abc); #define FF2() \ - class Test {}; + class Test {} - $expansion2[[FF2]](); + $expansion2parens[[$expansion2[[FF2]]()]]; #define FF3() \ void waldo() @@ -683,13 +754,21 @@ TEST(DocumentSymbols, FromMacro) { }]] )"); TU.Code = Main.code().str(); - EXPECT_THAT(getSymbols(TU.build()), - ElementsAre(AllOf(WithName("abc_Test"), WithDetail("class"), - SymNameRange(Main.range("expansion1"))), - AllOf(WithName("Test"), WithDetail("class"), - SymNameRange(Main.range("expansion2"))), - AllOf(WithName("waldo"), WithDetail("void ()"), - SymRange(Main.range("fullDef"))))); + EXPECT_THAT( + getSymbols(TU.build()), + ElementsAre( + AllOf(WithName("FF"), WithDetail("(abc)"), + Children(AllOf(WithName("abc_Test"), WithDetail("class"), + SymNameRange(Main.range("expansion1"))))), + AllOf(WithName("FF2"), WithDetail("()"), + SymNameRange(Main.range("expansion2")), + SymRange(Main.range("expansion2parens")), + Children(AllOf(WithName("Test"), WithDetail("class"), + SymNameRange(Main.range("expansion2"))))), + AllOf(WithName("FF3"), WithDetail("()"), + SymRange(Main.range("fullDef")), + Children(AllOf(WithName("waldo"), WithDetail("void ()"), + SymRange(Main.range("fullDef"))))))); } TEST(DocumentSymbols, FuncTemplates) { @@ -912,6 +991,42 @@ TEST(DocumentSymbolsTest, DependentType) { WithDetail("")))))); } +TEST(DocumentSymbolsTest, ObjCCategoriesAndClassExtensions) { + TestTU TU; + TU.ExtraArgs = {"-xobjective-c++", "-Wno-objc-root-class"}; + Annotations Main(R"cpp( + $Cat[[@interface Cat + + (id)sharedCat; + @end]] + $SneakyCat[[@interface Cat (Sneaky) + - (id)sneak:(id)behavior; + @end]] + + $MeowCat[[@interface Cat () + - (void)meow; + @end]] + $PurCat[[@interface Cat () + - (void)pur; + @end]] + )cpp"); + TU.Code = Main.code().str(); + EXPECT_THAT( + getSymbols(TU.build()), + ElementsAre( + AllOf(WithName("Cat"), SymRange(Main.range("Cat")), + Children(AllOf(WithName("+sharedCat"), + WithKind(SymbolKind::Method)))), + AllOf(WithName("Cat(Sneaky)"), SymRange(Main.range("SneakyCat")), + Children( + AllOf(WithName("-sneak:"), WithKind(SymbolKind::Method)))), + AllOf( + WithName("Cat()"), SymRange(Main.range("MeowCat")), + Children(AllOf(WithName("-meow"), WithKind(SymbolKind::Method)))), + AllOf(WithName("Cat()"), SymRange(Main.range("PurCat")), + Children( + AllOf(WithName("-pur"), WithKind(SymbolKind::Method)))))); +} + } // namespace } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/unittests/FindTargetTests.cpp b/clang-tools-extra/clangd/unittests/FindTargetTests.cpp index c9f035e7e971d80cf1eb547c79dca75a386122c5..7a25293bf2b9d5f1693520e6a259eb6fec0d55db 100644 --- a/clang-tools-extra/clangd/unittests/FindTargetTests.cpp +++ b/clang-tools-extra/clangd/unittests/FindTargetTests.cpp @@ -1593,6 +1593,35 @@ TEST_F(FindExplicitReferencesTest, All) { "0: targets = {f}\n" "1: targets = {I::x}\n" "2: targets = {I::setY:}\n"}, + { + // Objective-C: methods + R"cpp( + @interface I + -(void) a:(int)x b:(int)y; + @end + void foo(I *i) { + [$0^i $1^a:1 b:2]; + } + )cpp", + "0: targets = {i}\n" + "1: targets = {I::a:b:}\n" + }, + { + // Objective-C: protocols + R"cpp( + @interface I + @end + @protocol P + @end + void foo() { + // FIXME: should reference P + $0^I

*$1^x; + } + )cpp", + "0: targets = {I}\n" + "1: targets = {x}, decl\n" + }, + // Designated initializers. {R"cpp( void foo() { diff --git a/clang-tools-extra/clangd/unittests/HoverTests.cpp b/clang-tools-extra/clangd/unittests/HoverTests.cpp index 77ff91c761aa1c3e1f345b6cb927207715f27467..6624290ec41c5f420458ea6b0a3c4b812fabc05d 100644 --- a/clang-tools-extra/clangd/unittests/HoverTests.cpp +++ b/clang-tools-extra/clangd/unittests/HoverTests.cpp @@ -490,30 +490,30 @@ class Foo {})cpp"; HI.Value = "3"; }}, {R"cpp( - enum Color { RED, GREEN, }; + enum Color { RED = -123, GREEN = 5, }; Color x = [[GR^EEN]]; )cpp", [](HoverInfo &HI) { HI.Name = "GREEN"; HI.NamespaceScope = ""; HI.LocalScope = "Color::"; - HI.Definition = "GREEN"; + HI.Definition = "GREEN = 5"; HI.Kind = index::SymbolKind::EnumConstant; HI.Type = "enum Color"; - HI.Value = "1"; // Numeric when hovering on the enumerator name. + HI.Value = "5"; // Numeric on the enumerator name, no hex as small. }}, {R"cpp( - enum Color { RED, GREEN, }; - Color x = GREEN; + enum Color { RED = -123, GREEN = 5, }; + Color x = RED; Color y = [[^x]]; )cpp", [](HoverInfo &HI) { HI.Name = "x"; HI.NamespaceScope = ""; - HI.Definition = "Color x = GREEN"; + HI.Definition = "Color x = RED"; HI.Kind = index::SymbolKind::Variable; HI.Type = "enum Color"; - HI.Value = "GREEN (1)"; // Symbolic when hovering on an expression. + HI.Value = "RED (0xffffff85)"; // Symbolic on an expression. }}, {R"cpp( template struct Add { @@ -543,7 +543,7 @@ class Foo {})cpp"; HI.ReturnType = "int"; HI.Parameters.emplace(); HI.NamespaceScope = ""; - HI.Value = "42"; + HI.Value = "42 (0x2a)"; }}, {R"cpp( const char *[[ba^r]] = "1234"; @@ -1468,12 +1468,12 @@ TEST(Hover, All) { HI.Definition = "static int hey = 10"; HI.Documentation = "Global variable"; // FIXME: Value shouldn't be set in this case - HI.Value = "10"; + HI.Value = "10 (0xa)"; }}, { R"cpp(// Global variable in namespace namespace ns1 { - static int hey = 10; + static long long hey = -36637162602497; } void foo() { ns1::[[he^y]]++; @@ -1483,9 +1483,9 @@ TEST(Hover, All) { HI.Name = "hey"; HI.Kind = index::SymbolKind::Variable; HI.NamespaceScope = "ns1::"; - HI.Type = "int"; - HI.Definition = "static int hey = 10"; - HI.Value = "10"; + HI.Type = "long long"; + HI.Definition = "static long long hey = -36637162602497"; + HI.Value = "-36637162602497 (0xffffdeadbeefffff)"; // needs 64 bits }}, { R"cpp(// Field in anonymous struct diff --git a/clang-tools-extra/clangd/unittests/IndexTests.cpp b/clang-tools-extra/clangd/unittests/IndexTests.cpp index 64aafc9f883e480c0800e1f0672fc858f23c7484..2b4b3af856131c9cbfb9440c55cde10c6cbd5e52 100644 --- a/clang-tools-extra/clangd/unittests/IndexTests.cpp +++ b/clang-tools-extra/clangd/unittests/IndexTests.cpp @@ -229,7 +229,7 @@ TEST(MemIndexTest, IndexedFiles) { RefSlab Refs; auto Size = Symbols.bytes() + Refs.bytes(); auto Data = std::make_pair(std::move(Symbols), std::move(Refs)); - llvm::StringSet<> Files = {testPath("foo.cc"), testPath("bar.cc")}; + llvm::StringSet<> Files = {"unittest:///foo.cc", "unittest:///bar.cc"}; MemIndex I(std::move(Data.first), std::move(Data.second), RelationSlab(), std::move(Files), IndexContents::All, std::move(Data), Size); auto ContainsFile = I.indexedFiles(); @@ -506,7 +506,7 @@ TEST(MergeIndexTest, IndexedFiles) { RefSlab DynRefs; auto DynSize = DynSymbols.bytes() + DynRefs.bytes(); auto DynData = std::make_pair(std::move(DynSymbols), std::move(DynRefs)); - llvm::StringSet<> DynFiles = {testPath("foo.cc")}; + llvm::StringSet<> DynFiles = {"unittest:///foo.cc"}; MemIndex DynIndex(std::move(DynData.first), std::move(DynData.second), RelationSlab(), std::move(DynFiles), IndexContents::Symbols, std::move(DynData), DynSize); @@ -514,7 +514,7 @@ TEST(MergeIndexTest, IndexedFiles) { RefSlab StaticRefs; auto StaticData = std::make_pair(std::move(StaticSymbols), std::move(StaticRefs)); - llvm::StringSet<> StaticFiles = {testPath("foo.cc"), testPath("bar.cc")}; + llvm::StringSet<> StaticFiles = {"unittest:///foo.cc", "unittest:///bar.cc"}; MemIndex StaticIndex( std::move(StaticData.first), std::move(StaticData.second), RelationSlab(), std::move(StaticFiles), IndexContents::References, std::move(StaticData), diff --git a/clang-tools-extra/clangd/unittests/RenameTests.cpp b/clang-tools-extra/clangd/unittests/RenameTests.cpp index 5e16640ccded1dc0122cabc01e6aa47a1a93e013..ca0e7ff243061ae3ae736dd24a0cd1aa4d64c299 100644 --- a/clang-tools-extra/clangd/unittests/RenameTests.cpp +++ b/clang-tools-extra/clangd/unittests/RenameTests.cpp @@ -33,6 +33,19 @@ using testing::SizeIs; using testing::UnorderedElementsAre; using testing::UnorderedElementsAreArray; +llvm::IntrusiveRefCntPtr +createOverlay(llvm::IntrusiveRefCntPtr Base, + llvm::IntrusiveRefCntPtr Overlay) { + auto OFS = + llvm::makeIntrusiveRefCnt(std::move(Base)); + OFS->pushOverlay(std::move(Overlay)); + return OFS; +} + +llvm::IntrusiveRefCntPtr getVFSFromAST(ParsedAST &AST) { + return &AST.getSourceManager().getFileManager().getVirtualFileSystem(); +} + // Convert a Range to a Ref. Ref refWithRange(const clangd::Range &Range, const std::string &URI) { Ref Result; @@ -815,7 +828,8 @@ TEST(RenameTest, WithinFileRename) { auto Index = TU.index(); for (const auto &RenamePos : Code.points()) { auto RenameResult = - rename({RenamePos, NewName, AST, testPath(TU.Filename), Index.get()}); + rename({RenamePos, NewName, AST, testPath(TU.Filename), + getVFSFromAST(AST), Index.get()}); ASSERT_TRUE(bool(RenameResult)) << RenameResult.takeError(); ASSERT_EQ(1u, RenameResult->GlobalChanges.size()); EXPECT_EQ( @@ -1101,13 +1115,21 @@ TEST(RenameTest, IndexMergeMainFile) { auto AST = TU.build(); auto Main = testPath("main.cc"); + auto InMemFS = llvm::makeIntrusiveRefCnt(); + InMemFS->addFile(testPath("main.cc"), 0, + llvm::MemoryBuffer::getMemBuffer(Code.code())); + InMemFS->addFile(testPath("other.cc"), 0, + llvm::MemoryBuffer::getMemBuffer(Code.code())); auto Rename = [&](const SymbolIndex *Idx) { - auto GetDirtyBuffer = [&](PathRef Path) -> llvm::Optional { - return Code.code().str(); // Every file has the same content. - }; - RenameInputs Inputs{Code.point(), "xPrime", AST, Main, - Idx, RenameOptions(), GetDirtyBuffer}; + RenameInputs Inputs{Code.point(), + "xPrime", + AST, + Main, + Idx ? createOverlay(getVFSFromAST(AST), InMemFS) + : nullptr, + Idx, + RenameOptions()}; auto Results = rename(Inputs); EXPECT_TRUE(bool(Results)) << llvm::toString(Results.takeError()); return std::move(*Results); @@ -1237,25 +1259,19 @@ TEST(CrossFileRenameTests, DirtyBuffer) { Annotations MainCode("class [[Fo^o]] {};"); auto MainFilePath = testPath("main.cc"); - // Dirty buffer for foo.cc. - auto GetDirtyBuffer = [&](PathRef Path) -> llvm::Optional { - if (Path == FooPath) - return FooDirtyBuffer.code().str(); - return llvm::None; - }; + llvm::IntrusiveRefCntPtr InMemFS = + new llvm::vfs::InMemoryFileSystem; + InMemFS->addFile(FooPath, 0, + llvm::MemoryBuffer::getMemBuffer(FooDirtyBuffer.code())); // Run rename on Foo, there is a dirty buffer for foo.cc, rename should // respect the dirty buffer. TestTU TU = TestTU::withCode(MainCode.code()); auto AST = TU.build(); llvm::StringRef NewName = "newName"; - auto Results = rename({MainCode.point(), - NewName, - AST, - MainFilePath, - Index.get(), - {}, - GetDirtyBuffer}); + auto Results = + rename({MainCode.point(), NewName, AST, MainFilePath, + createOverlay(getVFSFromAST(AST), InMemFS), Index.get()}); ASSERT_TRUE(bool(Results)) << Results.takeError(); EXPECT_THAT( applyEdits(std::move(Results->GlobalChanges)), @@ -1270,13 +1286,8 @@ TEST(CrossFileRenameTests, DirtyBuffer) { // Set a file "bar.cc" on disk. TU.AdditionalFiles["bar.cc"] = std::string(BarCode.code()); AST = TU.build(); - Results = rename({MainCode.point(), - NewName, - AST, - MainFilePath, - Index.get(), - {}, - GetDirtyBuffer}); + Results = rename({MainCode.point(), NewName, AST, MainFilePath, + createOverlay(getVFSFromAST(AST), InMemFS), Index.get()}); ASSERT_TRUE(bool(Results)) << Results.takeError(); EXPECT_THAT( applyEdits(std::move(Results->GlobalChanges)), @@ -1312,13 +1323,8 @@ TEST(CrossFileRenameTests, DirtyBuffer) { size_t estimateMemoryUsage() const override { return 0; } } PIndex; - Results = rename({MainCode.point(), - NewName, - AST, - MainFilePath, - &PIndex, - {}, - GetDirtyBuffer}); + Results = rename({MainCode.point(), NewName, AST, MainFilePath, + createOverlay(getVFSFromAST(AST), InMemFS), &PIndex}); EXPECT_FALSE(Results); EXPECT_THAT(llvm::toString(Results.takeError()), testing::HasSubstr("too many occurrences")); @@ -1368,8 +1374,8 @@ TEST(CrossFileRenameTests, DeduplicateRefsFromIndex) { Ref ReturnedRef; } DIndex(XRefInBarCC); llvm::StringRef NewName = "newName"; - auto Results = - rename({MainCode.point(), NewName, AST, MainFilePath, &DIndex}); + auto Results = rename({MainCode.point(), NewName, AST, MainFilePath, + getVFSFromAST(AST), &DIndex}); ASSERT_TRUE(bool(Results)) << Results.takeError(); EXPECT_THAT( applyEdits(std::move(Results->GlobalChanges)), diff --git a/clang-tools-extra/clangd/unittests/SemanticHighlightingTests.cpp b/clang-tools-extra/clangd/unittests/SemanticHighlightingTests.cpp index eb848dddad20adcd47911e3ce363ccf1d9ea8e29..d8dc3b061df5e2e281e2e48ce3444fa6af98e99b 100644 --- a/clang-tools-extra/clangd/unittests/SemanticHighlightingTests.cpp +++ b/clang-tools-extra/clangd/unittests/SemanticHighlightingTests.cpp @@ -75,6 +75,7 @@ void checkHighlightings(llvm::StringRef Code, TU.Code = std::string(Test.code()); TU.ExtraArgs.push_back("-std=c++20"); + TU.ExtraArgs.push_back("-xobjective-c++"); for (auto File : AdditionalFiles) TU.AdditionalFiles.insert({File.first, std::string(File.second)}); @@ -645,6 +646,48 @@ sizeof...($TemplateParameter[[Elements]]); R"cpp( <:[deprecated]:> int $Variable_decl_deprecated[[x]]; )cpp", + R"cpp( + // ObjC: Classes and methods + @class $Class_decl[[Forward]]; + + @interface $Class_decl[[Foo]] + @end + @interface $Class_decl[[Bar]] : $Class[[Foo]] + -($Class[[id]]) $Method_decl[[x]]:(int)$Parameter_decl[[a]] $Method_decl[[y]]:(int)$Parameter_decl[[b]]; + +(void) $StaticMethod_decl_static[[explode]]; + @end + @implementation $Class_decl[[Bar]] + -($Class[[id]]) $Method_decl[[x]]:(int)$Parameter_decl[[a]] $Method_decl[[y]]:(int)$Parameter_decl[[b]] { + return self; + } + +(void) $StaticMethod_decl_static[[explode]] {} + @end + + void $Function_decl[[m]]($Class[[Bar]] *$Parameter_decl[[b]]) { + [$Parameter[[b]] $Method[[x]]:1 $Method[[y]]:2]; + [$Class[[Bar]] $StaticMethod_static[[explode]]]; + } + )cpp", + R"cpp( + // ObjC: Protocols + @protocol $Interface_decl[[Protocol]] + @end + @protocol $Interface_decl[[Protocol2]] <$Interface[[Protocol]]> + @end + @interface $Class_decl[[Klass]] <$Interface[[Protocol]]> + @end + // FIXME: protocol list in ObjCObjectType should be highlighted. + id $Variable_decl[[x]]; + )cpp", + R"cpp( + // ObjC: Categories + @interface $Class_decl[[Foo]] + @end + @interface $Class[[Foo]]($Namespace_decl[[Bar]]) + @end + @implementation $Class[[Foo]]($Namespace_decl[[Bar]]) + @end + )cpp", }; for (const auto &TestCase : TestCases) // Mask off scope modifiers to keep the tests manageable. diff --git a/clang-tools-extra/clangd/unittests/SourceCodeTests.cpp b/clang-tools-extra/clangd/unittests/SourceCodeTests.cpp index c05515f2c094f5f495b9ff0b91650c14ee18f86f..bfe0e430b775acb2a1372d403b8bda18c6e3987b 100644 --- a/clang-tools-extra/clangd/unittests/SourceCodeTests.cpp +++ b/clang-tools-extra/clangd/unittests/SourceCodeTests.cpp @@ -27,6 +27,7 @@ namespace clangd { namespace { using llvm::Failed; +using llvm::FailedWithMessage; using llvm::HasValue; MATCHER_P2(Pos, Line, Col, "") { @@ -802,6 +803,226 @@ TEST(SourceCodeTests, isKeywords) { EXPECT_FALSE(isKeyword("override", LangOpts)); } +struct IncrementalTestStep { + llvm::StringRef Src; + llvm::StringRef Contents; +}; + +int rangeLength(llvm::StringRef Code, const Range &Rng) { + llvm::Expected Start = positionToOffset(Code, Rng.start); + llvm::Expected End = positionToOffset(Code, Rng.end); + assert(Start); + assert(End); + return *End - *Start; +} + +/// Send the changes one by one to updateDraft, verify the intermediate results. +void stepByStep(llvm::ArrayRef Steps) { + std::string Code = Annotations(Steps.front().Src).code().str(); + + for (size_t I = 1; I < Steps.size(); I++) { + Annotations SrcBefore(Steps[I - 1].Src); + Annotations SrcAfter(Steps[I].Src); + llvm::StringRef Contents = Steps[I - 1].Contents; + TextDocumentContentChangeEvent Event{ + SrcBefore.range(), + rangeLength(SrcBefore.code(), SrcBefore.range()), + Contents.str(), + }; + + EXPECT_THAT_ERROR(applyChange(Code, Event), llvm::Succeeded()); + EXPECT_EQ(Code, SrcAfter.code()); + } +} + +TEST(ApplyEditsTest, Simple) { + // clang-format off + IncrementalTestStep Steps[] = + { + // Replace a range + { +R"cpp(static int +hello[[World]]() +{})cpp", + "Universe" + }, + // Delete a range + { +R"cpp(static int +hello[[Universe]]() +{})cpp", + "" + }, + // Add a range + { +R"cpp(static int +hello[[]]() +{})cpp", + "Monde" + }, + { +R"cpp(static int +helloMonde() +{})cpp", + "" + } + }; + // clang-format on + + stepByStep(Steps); +} + +TEST(ApplyEditsTest, MultiLine) { + // clang-format off + IncrementalTestStep Steps[] = + { + // Replace a range + { +R"cpp(static [[int +helloWorld]]() +{})cpp", +R"cpp(char +welcome)cpp" + }, + // Delete a range + { +R"cpp(static char[[ +welcome]]() +{})cpp", + "" + }, + // Add a range + { +R"cpp(static char[[]]() +{})cpp", + R"cpp( +cookies)cpp" + }, + // Replace the whole file + { +R"cpp([[static char +cookies() +{}]])cpp", + R"cpp(#include +)cpp" + }, + // Delete the whole file + { + R"cpp([[#include +]])cpp", + "", + }, + // Add something to an empty file + { + "[[]]", + R"cpp(int main() { +)cpp", + }, + { + R"cpp(int main() { +)cpp", + "" + } + }; + // clang-format on + + stepByStep(Steps); +} + +TEST(ApplyEditsTest, WrongRangeLength) { + std::string Code = "int main() {}\n"; + + TextDocumentContentChangeEvent Change; + Change.range.emplace(); + Change.range->start.line = 0; + Change.range->start.character = 0; + Change.range->end.line = 0; + Change.range->end.character = 2; + Change.rangeLength = 10; + + EXPECT_THAT_ERROR(applyChange(Code, Change), + FailedWithMessage("Change's rangeLength (10) doesn't match " + "the computed range length (2).")); +} + +TEST(ApplyEditsTest, EndBeforeStart) { + std::string Code = "int main() {}\n"; + + TextDocumentContentChangeEvent Change; + Change.range.emplace(); + Change.range->start.line = 0; + Change.range->start.character = 5; + Change.range->end.line = 0; + Change.range->end.character = 3; + + EXPECT_THAT_ERROR( + applyChange(Code, Change), + FailedWithMessage( + "Range's end position (0:3) is before start position (0:5)")); +} + +TEST(ApplyEditsTest, StartCharOutOfRange) { + std::string Code = "int main() {}\n"; + + TextDocumentContentChangeEvent Change; + Change.range.emplace(); + Change.range->start.line = 0; + Change.range->start.character = 100; + Change.range->end.line = 0; + Change.range->end.character = 100; + Change.text = "foo"; + + EXPECT_THAT_ERROR( + applyChange(Code, Change), + FailedWithMessage("utf-16 offset 100 is invalid for line 0")); +} + +TEST(ApplyEditsTest, EndCharOutOfRange) { + std::string Code = "int main() {}\n"; + + TextDocumentContentChangeEvent Change; + Change.range.emplace(); + Change.range->start.line = 0; + Change.range->start.character = 0; + Change.range->end.line = 0; + Change.range->end.character = 100; + Change.text = "foo"; + + EXPECT_THAT_ERROR( + applyChange(Code, Change), + FailedWithMessage("utf-16 offset 100 is invalid for line 0")); +} + +TEST(ApplyEditsTest, StartLineOutOfRange) { + std::string Code = "int main() {}\n"; + + TextDocumentContentChangeEvent Change; + Change.range.emplace(); + Change.range->start.line = 100; + Change.range->start.character = 0; + Change.range->end.line = 100; + Change.range->end.character = 0; + Change.text = "foo"; + + EXPECT_THAT_ERROR(applyChange(Code, Change), + FailedWithMessage("Line value is out of range (100)")); +} + +TEST(ApplyEditsTest, EndLineOutOfRange) { + std::string Code = "int main() {}\n"; + + TextDocumentContentChangeEvent Change; + Change.range.emplace(); + Change.range->start.line = 0; + Change.range->start.character = 0; + Change.range->end.line = 100; + Change.range->end.character = 0; + Change.text = "foo"; + + EXPECT_THAT_ERROR(applyChange(Code, Change), + FailedWithMessage("Line value is out of range (100)")); +} + } // namespace } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/unittests/SyncAPI.cpp b/clang-tools-extra/clangd/unittests/SyncAPI.cpp index 27b6cf33e0559fb0c0b0c0d4eb989ae11920764c..8c18148a1d3078cbe51e638b1110c8e2e1f8bdeb 100644 --- a/clang-tools-extra/clangd/unittests/SyncAPI.cpp +++ b/clang-tools-extra/clangd/unittests/SyncAPI.cpp @@ -115,9 +115,9 @@ runPrepareRename(ClangdServer &Server, PathRef File, Position Pos, } llvm::Expected -runFormatFile(ClangdServer &Server, PathRef File, StringRef Code) { +runFormatFile(ClangdServer &Server, PathRef File, llvm::Optional Rng) { llvm::Optional> Result; - Server.formatFile(File, Code, capture(Result)); + Server.formatFile(File, Rng, capture(Result)); return std::move(*Result); } diff --git a/clang-tools-extra/clangd/unittests/SyncAPI.h b/clang-tools-extra/clangd/unittests/SyncAPI.h index fd0f5dba604deaab8f0a999f0d6d2867b78b7f9c..71fdf33ce720ad74ec9f82a75277f60df01cf979 100644 --- a/clang-tools-extra/clangd/unittests/SyncAPI.h +++ b/clang-tools-extra/clangd/unittests/SyncAPI.h @@ -50,7 +50,7 @@ runPrepareRename(ClangdServer &Server, PathRef File, Position Pos, const clangd::RenameOptions &RenameOpts); llvm::Expected -runFormatFile(ClangdServer &Server, PathRef File, StringRef Code); +runFormatFile(ClangdServer &Server, PathRef File, llvm::Optional); SymbolSlab runFuzzyFind(const SymbolIndex &Index, StringRef Query); SymbolSlab runFuzzyFind(const SymbolIndex &Index, const FuzzyFindRequest &Req); diff --git a/clang-tools-extra/clangd/unittests/TUSchedulerTests.cpp b/clang-tools-extra/clangd/unittests/TUSchedulerTests.cpp index 0c9455f0eaf63b02db87c7509400620732cc8261..22b6ea2296d2b403696a38cea7b2f0fdd174a5af 100644 --- a/clang-tools-extra/clangd/unittests/TUSchedulerTests.cpp +++ b/clang-tools-extra/clangd/unittests/TUSchedulerTests.cpp @@ -9,6 +9,7 @@ #include "Annotations.h" #include "ClangdServer.h" #include "Diagnostics.h" +#include "GlobalCompilationDatabase.h" #include "Matchers.h" #include "ParsedAST.h" #include "Preamble.h" @@ -43,12 +44,15 @@ namespace clang { namespace clangd { namespace { +using ::testing::AllOf; using ::testing::AnyOf; +using ::testing::Contains; using ::testing::Each; using ::testing::ElementsAre; using ::testing::Eq; using ::testing::Field; using ::testing::IsEmpty; +using ::testing::Not; using ::testing::Pair; using ::testing::Pointee; using ::testing::SizeIs; @@ -1161,6 +1165,105 @@ TEST_F(TUSchedulerTests, AsyncPreambleThread) { Ready.notify(); } +// If a header file is missing from the CDB (or inferred using heuristics), and +// it's included by another open file, then we parse it using that files flags. +TEST_F(TUSchedulerTests, IncluderCache) { + static std::string Main = testPath("main.cpp"), Main2 = testPath("main2.cpp"), + Main3 = testPath("main3.cpp"), + NoCmd = testPath("no_cmd.h"), + Unreliable = testPath("unreliable.h"), + OK = testPath("ok.h"), + NotIncluded = testPath("not_included.h"); + class NoHeadersCDB : public GlobalCompilationDatabase { + llvm::Optional + getCompileCommand(PathRef File) const override { + if (File == NoCmd || File == NotIncluded) + return llvm::None; + auto Basic = getFallbackCommand(File); + Basic.Heuristic.clear(); + if (File == Unreliable) { + Basic.Heuristic = "not reliable"; + } else if (File == Main) { + Basic.CommandLine.push_back("-DMAIN"); + } else if (File == Main2) { + Basic.CommandLine.push_back("-DMAIN2"); + } else if (File == Main3) { + Basic.CommandLine.push_back("-DMAIN3"); + } + return Basic; + } + } CDB; + TUScheduler S(CDB, optsForTest()); + auto GetFlags = [&](PathRef Header) { + S.update(Header, getInputs(Header, ";"), WantDiagnostics::Yes); + EXPECT_TRUE(S.blockUntilIdle(timeoutSeconds(10))); + tooling::CompileCommand Cmd; + S.runWithPreamble("GetFlags", Header, TUScheduler::StaleOrAbsent, + [&](llvm::Expected Inputs) { + ASSERT_FALSE(!Inputs) << Inputs.takeError(); + Cmd = std::move(Inputs->Command); + }); + EXPECT_TRUE(S.blockUntilIdle(timeoutSeconds(10))); + return Cmd.CommandLine; + }; + + for (const auto &Path : {NoCmd, Unreliable, OK, NotIncluded}) + FS.Files[Path] = ";"; + + // Initially these files have normal commands from the CDB. + EXPECT_THAT(GetFlags(Main), Contains("-DMAIN")) << "sanity check"; + EXPECT_THAT(GetFlags(NoCmd), Not(Contains("-DMAIN"))) << "no includes yet"; + + // Now make Main include the others, and some should pick up its flags. + const char *AllIncludes = R"cpp( + #include "no_cmd.h" + #include "ok.h" + #include "unreliable.h" + )cpp"; + S.update(Main, getInputs(Main, AllIncludes), WantDiagnostics::Yes); + EXPECT_TRUE(S.blockUntilIdle(timeoutSeconds(10))); + EXPECT_THAT(GetFlags(NoCmd), Contains("-DMAIN")) + << "Included from main file, has no own command"; + EXPECT_THAT(GetFlags(Unreliable), Contains("-DMAIN")) + << "Included from main file, own command is heuristic"; + EXPECT_THAT(GetFlags(OK), Not(Contains("-DMAIN"))) + << "Included from main file, but own command is used"; + EXPECT_THAT(GetFlags(NotIncluded), Not(Contains("-DMAIN"))) + << "Not included from main file"; + + // Open another file - it won't overwrite the associations with Main. + std::string SomeIncludes = R"cpp( + #include "no_cmd.h" + #include "not_included.h" + )cpp"; + S.update(Main2, getInputs(Main2, SomeIncludes), WantDiagnostics::Yes); + EXPECT_TRUE(S.blockUntilIdle(timeoutSeconds(10))); + EXPECT_THAT(GetFlags(NoCmd), + AllOf(Contains("-DMAIN"), Not(Contains("-DMAIN2")))) + << "mainfile association is stable"; + EXPECT_THAT(GetFlags(NotIncluded), + AllOf(Contains("-DMAIN2"), Not(Contains("-DMAIN")))) + << "new headers are associated with new mainfile"; + + // Remove includes from main - this marks the associations as invalid but + // doesn't actually remove them until another preamble claims them. + S.update(Main, getInputs(Main, ""), WantDiagnostics::Yes); + EXPECT_TRUE(S.blockUntilIdle(timeoutSeconds(10))); + EXPECT_THAT(GetFlags(NoCmd), + AllOf(Contains("-DMAIN"), Not(Contains("-DMAIN2")))) + << "mainfile association not updated yet!"; + + // Open yet another file - this time it claims the associations. + S.update(Main3, getInputs(Main3, SomeIncludes), WantDiagnostics::Yes); + EXPECT_TRUE(S.blockUntilIdle(timeoutSeconds(10))); + EXPECT_THAT(GetFlags(NoCmd), Contains("-DMAIN3")) + << "association invalidated and then claimed by main3"; + EXPECT_THAT(GetFlags(Unreliable), Contains("-DMAIN")) + << "association invalidated but not reclaimed"; + EXPECT_THAT(GetFlags(NotIncluded), Contains("-DMAIN2")) + << "association still valid"; +} + } // namespace } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/unittests/XRefsTests.cpp b/clang-tools-extra/clangd/unittests/XRefsTests.cpp index 9d77e0fb291a43648c6e30a4d8969b8f45b13dea..2fbf1f98db8a7b04e301958044b494917a94818d 100644 --- a/clang-tools-extra/clangd/unittests/XRefsTests.cpp +++ b/clang-tools-extra/clangd/unittests/XRefsTests.cpp @@ -115,10 +115,23 @@ TEST(HighlightsTest, All) { f.[[^~]]Foo(); } )cpp", + R"cpp(// ObjC methods with split selectors. + @interface Foo + +(void) [[x]]:(int)a [[y]]:(int)b; + @end + @implementation Foo + +(void) [[x]]:(int)a [[y]]:(int)b {} + @end + void go() { + [Foo [[x]]:2 [[^y]]:4]; + } + )cpp", }; for (const char *Test : Tests) { Annotations T(Test); - auto AST = TestTU::withCode(T.code()).build(); + auto TU = TestTU::withCode(T.code()); + TU.ExtraArgs.push_back("-xobjective-c++"); + auto AST = TU.build(); EXPECT_THAT(findDocumentHighlights(AST, T.point()), HighlightsFrom(T)) << Test; } diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst index 38b3b5205df1d64f7460c43e791e8cfd658f2fb9..91207090902d8b2fa12451c72d00a915d63a7fbf 100644 --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -70,6 +70,10 @@ Improvements to clang-tidy - The `run-clang-tidy.py` helper script is now installed in `bin/` as `run-clang-tidy`. It was previously installed in `share/clang/`. +- Added command line option `--fix-notes` to apply fixes found in notes + attached to warnings. These are typically cases where we are less confident + the fix will have the desired effect. + New checks ^^^^^^^^^^ @@ -101,6 +105,23 @@ Changes in existing checks Added an option to choose the set of allowed functions. +- Improved :doc:`readability-uniqueptr-delete-release + ` check. + + Added an option to choose whether to refactor by calling the ``reset`` member + function or assignment to ``nullptr``. + Added support for pointers to ``std::unique_ptr``. + +Deprecated checks +^^^^^^^^^^^^^^^^^ + +- The :doc:`readability-deleted-default + ` check has been deprecated. + + The clang warning `Wdefaulted-function-deleted + `_ + will diagnose the same issues and is enabled by default. + Improvements to include-fixer ----------------------------- diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone-use-after-move.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone-use-after-move.rst index 9fde912837d8a0251f6e3058e12588089caf64b2..8509292eff9947723ba6227bcc0d430278805dd6 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/bugprone-use-after-move.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone-use-after-move.rst @@ -24,6 +24,9 @@ move and before the use. For example, no warning will be output for this code: str = "Greetings, stranger!\n"; std::cout << str; +Subsections below explain more precisely what exactly the check considers to be +a move, use, and reinitialization. + The check takes control flow into account. A warning is only emitted if the use can be reached from the move. This means that the following code does not produce a warning: @@ -60,7 +63,12 @@ mutually exclusive. For example (assuming that ``i`` is an int): } In this case, the check will erroneously produce a warning, even though it is -not possible for both the move and the use to be executed. +not possible for both the move and the use to be executed. More formally, the +analysis is `flow-sensitive but not path-sensitive +`_. + +Silencing erroneous warnings +---------------------------- An erroneous warning can be silenced by reinitializing the object after the move: @@ -75,8 +83,30 @@ move: std::cout << str; } -Subsections below explain more precisely what exactly the check considers to be -a move, use, and reinitialization. +If you want to avoid the overhead of actually reinitializing the object, you can +create a dummy function that causes the check to assume the object was +reinitialized: + +.. code-block:: c++ + + template + void IS_INITIALIZED(T&) {} + +You can use this as follows: + +.. code-block:: c++ + + if (i == 1) { + messages.emplace_back(std::move(str)); + } + if (i == 2) { + IS_INITIALIZED(str); + std::cout << str; + } + +The check will not output a warning in this case because passing the object to a +function as a non-const pointer or reference counts as a reinitialization (see section +`Reinitialization`_ below). Unsequenced moves, uses, and reinitializations ---------------------------------------------- @@ -139,6 +169,10 @@ that a move always takes place: The check will assume that the last line causes a move, even though, in this particular case, it does not. Again, this is intentional. +There is one special case: A call to ``std::move`` inside a ``try_emplace`` call +is conservatively assumed not to move. This is to avoid spurious warnings, as +the check has no way to reason about the ``bool`` returned by ``try_emplace``. + When analyzing the order in which moves, uses and reinitializations happen (see section `Unsequenced moves, uses, and reinitializations`_), the move is assumed to occur in whichever function the result of the ``std::move`` is passed to. diff --git a/clang-tools-extra/docs/clang-tidy/checks/readability-deleted-default.rst b/clang-tools-extra/docs/clang-tidy/checks/readability-deleted-default.rst index 00134eb05484ce4c1862f5c23c805a370670061d..5f2083e00061aabaa7fb3837dd2f1c296c5febb0 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/readability-deleted-default.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/readability-deleted-default.rst @@ -3,20 +3,6 @@ readability-deleted-default =========================== -Checks that constructors and assignment operators marked as ``= default`` are -not actually deleted by the compiler. - -.. code-block:: c++ - - class Example { - public: - // This constructor is deleted because I is missing a default value. - Example() = default; - // This is fine. - Example(const Example& Other) = default; - // This operator is deleted because I cannot be assigned (it is const). - Example& operator=(const Example& Other) = default; - - private: - const int I; - }; +This check has been deprecated prefer to make use of the `Wdefaulted-function-deleted +`_ +flag. diff --git a/clang-tools-extra/docs/clang-tidy/checks/readability-function-cognitive-complexity.rst b/clang-tools-extra/docs/clang-tidy/checks/readability-function-cognitive-complexity.rst index b863357a2132614e03811db3bef52779623b50c1..b14c684f57f5c064f5779a22c6ac563dfe16983f 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/readability-function-cognitive-complexity.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/readability-function-cognitive-complexity.rst @@ -17,6 +17,13 @@ Options Flag functions with Cognitive Complexity exceeding this number. The default is `25`. +.. option:: DescribeBasicIncrements + + If set to `true`, then for each function exceeding the complexity threshold + the check will issue additional diagnostics on every piece of code (loop, + `if` statement, etc.) which contributes to that complexity. See also the + examples below. Default is `true`. + Building blocks --------------- @@ -135,6 +142,11 @@ Full example. This function has Cognitive Complexity of `3`. return 0; } +In the last example, the check will flag `function3` if the option Threshold is +set to `2` or smaller. If the option DescribeBasicIncrements is set to `true`, +it will additionally flag the two `if` statements with the amounts by which they +increase to the complexity of the function and the current nesting level. + Limitations ----------- diff --git a/clang-tools-extra/docs/clang-tidy/checks/readability-uniqueptr-delete-release.rst b/clang-tools-extra/docs/clang-tidy/checks/readability-uniqueptr-delete-release.rst index beec1d5fb6cb73c3a97bbe94de0a52f2066fdc99..b183af71704411fb08dc77ce6c42db2e575228cb 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/readability-uniqueptr-delete-release.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/readability-uniqueptr-delete-release.rst @@ -15,3 +15,21 @@ The latter is shorter, simpler and does not require use of raw pointer APIs. std::unique_ptr P; P = nullptr; + +Options +------- + +.. option:: PreferResetCall + + If `true`, refactor by calling the reset member function instead of + assigning to ``nullptr``. Default value is `false`. + + .. code-block:: c++ + + std::unique_ptr P; + delete P.release(); + + // becomes + + std::unique_ptr P; + P.reset(); diff --git a/clang-tools-extra/docs/clang-tidy/index.rst b/clang-tools-extra/docs/clang-tidy/index.rst index b8af4d34e2031a81b6f8223e13bfbda2ca86c0dd..63b895b7c0114e7c444bbeec449c761d5efa1be7 100644 --- a/clang-tools-extra/docs/clang-tidy/index.rst +++ b/clang-tools-extra/docs/clang-tidy/index.rst @@ -173,6 +173,12 @@ An overview of all the command-line options: errors were found. If compiler errors have attached fix-its, clang-tidy will apply them as well. + --fix-notes - + If a warning has no fix, but a single fix can + be found through an associated diagnostic note, + apply the fix. + Specifying this flag will implicitly enable the + '--fix' flag. --format-style= - Style for formatting code around applied fixes: - 'none' (default) turns off formatting diff --git a/clang-tools-extra/modularize/Modularize.cpp b/clang-tools-extra/modularize/Modularize.cpp index 73c78cff14b2b73e0c809df62a90613c6928d343..7f73749f5b540d89928c90ea3618fa11d5ffc376 100644 --- a/clang-tools-extra/modularize/Modularize.cpp +++ b/clang-tools-extra/modularize/Modularize.cpp @@ -247,7 +247,6 @@ #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Path.h" #include -#include #include #include #include diff --git a/clang-tools-extra/test/CMakeLists.txt b/clang-tools-extra/test/CMakeLists.txt index 662b138e9f461cf325fc1ed5de89c4531757d908..06be00015223064992c098d79aa2dfa19149997a 100644 --- a/clang-tools-extra/test/CMakeLists.txt +++ b/clang-tools-extra/test/CMakeLists.txt @@ -17,7 +17,6 @@ string(REPLACE ${CMAKE_CFG_INTDIR} ${LLVM_BUILD_MODE} CLANG_TOOLS_DIR ${LLVM_RUN llvm_canonicalize_cmake_booleans( CLANG_TIDY_ENABLE_STATIC_ANALYZER - LIBCLANG_INCLUDE_CLANG_TOOLS_EXTRA ) configure_lit_site_cfg( @@ -68,10 +67,6 @@ set(CLANG_TOOLS_TEST_DEPS # Clang-tidy tests need clang for building modules. clang ) -if (LIBCLANG_INCLUDE_CLANG_TOOLS_EXTRA) - # For the clang-tidy libclang integration test. - set(CLANG_TOOLS_TEST_DEPS ${CLANG_TOOLS_TEST_DEPS} "c-index-test") -endif () # Add lit test dependencies. set(LLVM_UTILS_DEPS diff --git a/clang-tools-extra/test/clang-tidy/checkers/abseil-faster-strsplit-delimiter.cpp b/clang-tools-extra/test/clang-tidy/checkers/abseil-faster-strsplit-delimiter.cpp index ff8c034d2b1f7d32b632a77b90017747cdbc1857..b5035941c9cebb730e4cc72b81d4af0bd1b00b16 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/abseil-faster-strsplit-delimiter.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/abseil-faster-strsplit-delimiter.cpp @@ -1,5 +1,4 @@ // RUN: %check_clang_tidy -std=c++11-or-later %s abseil-faster-strsplit-delimiter %t -// FIXME: Fix the checker to work in C++17 mode. namespace absl { diff --git a/clang-tools-extra/test/clang-tidy/checkers/abseil-time-subtraction.cpp b/clang-tools-extra/test/clang-tidy/checkers/abseil-time-subtraction.cpp index 6fd239bb457cf2933bb7a8d55c2e66dfe29cf73d..43d1feea1ec19976b9b687d59d9bde920fd5fc7b 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/abseil-time-subtraction.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/abseil-time-subtraction.cpp @@ -1,5 +1,4 @@ // RUN: %check_clang_tidy -std=c++11-or-later %s abseil-time-subtraction %t -- -- -I %S/Inputs -// FIXME: Fix the checker to work in C++17 mode. #include "absl/time/time.h" diff --git a/clang-tools-extra/test/clang-tidy/checkers/abseil-upgrade-duration-conversions.cpp b/clang-tools-extra/test/clang-tidy/checkers/abseil-upgrade-duration-conversions.cpp index db393a96a50d641049588c47fcb7bc66b680f5bc..32e65a63eb1c5b061ec7e346fb736e155115a58e 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/abseil-upgrade-duration-conversions.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/abseil-upgrade-duration-conversions.cpp @@ -1,5 +1,4 @@ // RUN: %check_clang_tidy -std=c++11-or-later %s abseil-upgrade-duration-conversions %t -- -- -I%S/Inputs -// FIXME: Fix the checker to work in C++17 mode. using int64_t = long long; diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone-unused-raii.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone-unused-raii.cpp index d637806ba20a69bc36c1b5f939cd2cadbb67ea6e..94643676f45ce2fe4626923dcdc942fa0274e497 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/bugprone-unused-raii.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone-unused-raii.cpp @@ -1,4 +1,4 @@ -// RUN: %check_clang_tidy %s bugprone-unused-raii %t +// RUN: %check_clang_tidy %s bugprone-unused-raii %t -- -- -fno-delayed-template-parsing struct Foo { Foo(); @@ -46,6 +46,42 @@ void neverInstantiated() { T(); } +struct CtorDefaultArg { + CtorDefaultArg(int i = 0); + ~CtorDefaultArg(); +}; + +template +struct TCtorDefaultArg { + TCtorDefaultArg(T i = 0); + ~TCtorDefaultArg(); +}; + +struct CtorTwoDefaultArg { + CtorTwoDefaultArg(int i = 0, bool b = false); + ~CtorTwoDefaultArg(); +}; + +template +void templatetest() { + TCtorDefaultArg(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: object destroyed immediately after creation; did you mean to name the object? + // CHECK-FIXES: TCtorDefaultArg give_me_a_name; + TCtorDefaultArg{}; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: object destroyed immediately after creation; did you mean to name the object? + // CHECK-FIXES: TCtorDefaultArg give_me_a_name; + + TCtorDefaultArg(T{}); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: object destroyed immediately after creation; did you mean to name the object? + // CHECK-FIXES: TCtorDefaultArg give_me_a_name(T{}); + TCtorDefaultArg{T{}}; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: object destroyed immediately after creation; did you mean to name the object? + // CHECK-FIXES: TCtorDefaultArg give_me_a_name{T{}}; + + int i = 0; + (void)i; +} + void test() { Foo(42); // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: object destroyed immediately after creation; did you mean to name the object? @@ -64,6 +100,29 @@ void test() { // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: object destroyed immediately after creation; did you mean to name the object? // CHECK-FIXES: FooBar give_me_a_name; + Foo{42}; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: object destroyed immediately after creation; did you mean to name the object? + // CHECK-FIXES: Foo give_me_a_name{42}; + FooBar{}; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: object destroyed immediately after creation; did you mean to name the object? + // CHECK-FIXES: FooBar give_me_a_name; + + CtorDefaultArg(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: object destroyed immediately after creation; did you mean to name the object? + // CHECK-FIXES: CtorDefaultArg give_me_a_name; + + CtorTwoDefaultArg(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: object destroyed immediately after creation; did you mean to name the object? + // CHECK-FIXES: CtorTwoDefaultArg give_me_a_name; + + TCtorDefaultArg(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: object destroyed immediately after creation; did you mean to name the object? + // CHECK-FIXES: TCtorDefaultArg give_me_a_name; + + TCtorDefaultArg{}; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: object destroyed immediately after creation; did you mean to name the object? + // CHECK-FIXES: TCtorDefaultArg give_me_a_name; + templ(); templ(); diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone-use-after-move.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone-use-after-move.cpp index 527c7984069651f3c6a5333782448174e3c080f7..73ca59ccc91bfa7208a0479ab9af95aed1cd3aa9 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/bugprone-use-after-move.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone-use-after-move.cpp @@ -32,6 +32,31 @@ struct weak_ptr { bool expired() const; }; +template +struct pair {}; + +template +struct map { + struct iterator {}; + + map(); + void clear(); + bool empty(); + template + pair try_emplace(const Key &key, Args &&...args); +}; + +template +struct unordered_map { + struct iterator {}; + + unordered_map(); + void clear(); + bool empty(); + template + pair try_emplace(const Key &key, Args &&...args); +}; + #define DECLARE_STANDARD_CONTAINER(name) \ template \ struct name { \ @@ -55,11 +80,9 @@ DECLARE_STANDARD_CONTAINER_WITH_ASSIGN(deque); DECLARE_STANDARD_CONTAINER_WITH_ASSIGN(forward_list); DECLARE_STANDARD_CONTAINER_WITH_ASSIGN(list); DECLARE_STANDARD_CONTAINER(set); -DECLARE_STANDARD_CONTAINER(map); DECLARE_STANDARD_CONTAINER(multiset); DECLARE_STANDARD_CONTAINER(multimap); DECLARE_STANDARD_CONTAINER(unordered_set); -DECLARE_STANDARD_CONTAINER(unordered_map); DECLARE_STANDARD_CONTAINER(unordered_multiset); DECLARE_STANDARD_CONTAINER(unordered_multimap); @@ -483,6 +506,22 @@ class IgnoreMemberVariables { } }; +// Ignore moves that happen in a try_emplace. +void ignoreMoveInTryEmplace() { + { + std::map amap; + A a; + amap.try_emplace(1, std::move(a)); + a.foo(); + } + { + std::unordered_map amap; + A a; + amap.try_emplace(1, std::move(a)); + a.foo(); + } +} + //////////////////////////////////////////////////////////////////////////////// // Tests involving control flow. @@ -776,7 +815,7 @@ void standardContainerClearIsReinit() { container.empty(); } { - std::map container; + std::map container; std::move(container); container.clear(); container.empty(); @@ -800,7 +839,7 @@ void standardContainerClearIsReinit() { container.empty(); } { - std::unordered_map container; + std::unordered_map container; std::move(container); container.clear(); container.empty(); diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone-virtual-near-miss.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone-virtual-near-miss.cpp index f3f8d3225847e06349776d9ab80ea56c26a43302..553d2f45a98bf546455a014d2f671705dc575632 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/bugprone-virtual-near-miss.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone-virtual-near-miss.cpp @@ -44,8 +44,9 @@ template struct TDerived : TBase { virtual void tfunk(T t); // Should not apply fix for template. - // CHECK-MESSAGES: :[[@LINE-2]]:3: warning: method 'TDerived::tfunk' has {{.*}} 'TBase::tfunc' - // CHECK-FIXES: virtual void tfunc(T t); + // CHECK-MESSAGES: :[[@LINE-2]]:3: warning: method 'TDerived::tfunk' has {{.*}} 'TBase::tfunc' + // CHECK-MESSAGES: :[[@LINE-3]]:3: warning: method 'TDerived::tfunk' has {{.*}} 'TBase::tfunc' + // CHECK-FIXES: virtual void tfunk(T t); }; TDerived T1; diff --git a/clang-tools-extra/test/clang-tidy/checkers/google-readability-casting.cpp b/clang-tools-extra/test/clang-tidy/checkers/google-readability-casting.cpp index 4655e27cd8bc9094f664ffd296c92cc82cc9a8e8..bddc28c2d81e2ccb743b1f7249b4a94de7dc5b9c 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/google-readability-casting.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/google-readability-casting.cpp @@ -1,5 +1,4 @@ // RUN: %check_clang_tidy -std=c++11-or-later %s google-readability-casting %t -// FIXME: Fix the checker to work in C++17 mode. bool g() { return false; } diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc-definitions-in-headers.hpp b/clang-tools-extra/test/clang-tidy/checkers/misc-definitions-in-headers.hpp index 25009ebe2bbdaad8b6a74f6840220a16b367583c..7b017391d5acb81dc52604ef688c9a2a7d48d2c9 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/misc-definitions-in-headers.hpp +++ b/clang-tools-extra/test/clang-tidy/checkers/misc-definitions-in-headers.hpp @@ -1,4 +1,4 @@ -// RUN: %check_clang_tidy %s misc-definitions-in-headers %t +// RUN: %check_clang_tidy %s misc-definitions-in-headers %t -- --fix-notes int f() { // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: function 'f' defined in a header file; function definitions in header files can lead to ODR violations [misc-definitions-in-headers] diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc-static-assert.cpp b/clang-tools-extra/test/clang-tidy/checkers/misc-static-assert.cpp index 85ae053cffef95a16ee1b32d5ee35ac53991743e..d4d083640d1629a9c39c95739fcbb854844456f9 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/misc-static-assert.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/misc-static-assert.cpp @@ -1,4 +1,5 @@ -// RUN: %check_clang_tidy %s misc-static-assert %t +// RUN: %check_clang_tidy -std=c++11 -check-suffixes=,CXX11 %s misc-static-assert %t +// RUN: %check_clang_tidy -std=c++17 -check-suffixes=,CXX17 %s misc-static-assert %t void abort() {} #ifdef NDEBUG @@ -37,7 +38,8 @@ public: template void doSomething(T t) { assert(myfunc(1, 2)); // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: found assert() that could be replaced by static_assert() [misc-static-assert] - // CHECK-FIXES: {{^ }}static_assert(myfunc(1, 2), ""); + // CHECK-FIXES-CXX11: {{^ }}static_assert(myfunc(1, 2), ""); + // CHECK-FIXES-CXX17: {{^ }}static_assert(myfunc(1, 2)); assert(t.method()); // CHECK-FIXES: {{^ }}assert(t.method()); @@ -52,7 +54,8 @@ int main() { assert(myfunc(1, 2) && (3 == 4)); // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: found assert() that could be - // CHECK-FIXES: {{^ }}static_assert(myfunc(1, 2) && (3 == 4), ""); + // CHECK-FIXES-CXX11: {{^ }}static_assert(myfunc(1, 2) && (3 == 4), ""); + // CHECK-FIXES-CXX17: {{^ }}static_assert(myfunc(1, 2) && (3 == 4)); int x = 1; assert(x == 0); @@ -74,7 +77,8 @@ int main() { assert(ZERO_MACRO); // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: found assert() that could be - // CHECK-FIXES: {{^ }}static_assert(ZERO_MACRO, ""); + // CHECK-FIXES-CXX11: {{^ }}static_assert(ZERO_MACRO, ""); + // CHECK-FIXES-CXX17: {{^ }}static_assert(ZERO_MACRO); assert(!"Don't report me!"); // CHECK-FIXES: {{^ }}assert(!"Don't report me!"); @@ -136,7 +140,8 @@ int main() { assert(10 == 5 + 5); // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: found assert() that could be - // CHECK-FIXES: {{^ }}static_assert(10 == 5 + 5, ""); + // CHECK-FIXES-CXX11: {{^ }}static_assert(10 == 5 + 5, ""); + // CHECK-FIXES-CXX17: {{^ }}static_assert(10 == 5 + 5); #undef assert return 0; diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc-uniqueptr-reset-release.cpp b/clang-tools-extra/test/clang-tidy/checkers/misc-uniqueptr-reset-release.cpp index 1bd6e6f4c5475e685a244ce3587faa9f202e69c4..befd2a0576d2d75b7ed9e01cb7126b40dcbe42ea 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/misc-uniqueptr-reset-release.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/misc-uniqueptr-reset-release.cpp @@ -33,27 +33,27 @@ void f() { std::unique_ptr *y = &b; a.reset(b.release()); - // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: prefer ptr1 = std::move(ptr2) over ptr1.reset(ptr2.release()) [misc-uniqueptr-reset-release] + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: prefer 'unique_ptr<>' assignment over 'release' and 'reset' [misc-uniqueptr-reset-release] // CHECK-FIXES: a = std::move(b); a.reset(c.release()); - // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: prefer ptr1 = std::move(ptr2) + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: prefer 'unique_ptr<>' assignment // CHECK-FIXES: a = std::move(c); a.reset(Create().release()); - // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: prefer ptr = ReturnUnique() over ptr.reset(ReturnUnique().release()) [misc-uniqueptr-reset-release] + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: prefer 'unique_ptr<>' assignment // CHECK-FIXES: a = Create(); x->reset(y->release()); - // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: prefer ptr1 = std::move(ptr2) + // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: prefer 'unique_ptr<>' assignment // CHECK-FIXES: *x = std::move(*y); Look().reset(Look().release()); - // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: prefer ptr1 = std::move(ptr2) + // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: prefer 'unique_ptr<>' assignment // CHECK-FIXES: Look() = std::move(Look()); Get()->reset(Get()->release()); - // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: prefer ptr1 = std::move(ptr2) + // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: prefer 'unique_ptr<>' assignment // CHECK-FIXES: *Get() = std::move(*Get()); std::unique_ptr func_a, func_b; func_a.reset(func_b.release()); - // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: prefer ptr1 = std::move(ptr2) + // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: prefer 'unique_ptr<>' assignment // CHECK-FIXES: func_a = std::move(func_b); } diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc-unused-using-decls-cxx17.cpp b/clang-tools-extra/test/clang-tidy/checkers/misc-unused-using-decls-cxx17.cpp index ca17e44be8e68d832b609b436ffa6f6e25fe9216..2c2edcf4df77851f937e28a6d736673377532856 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/misc-unused-using-decls-cxx17.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/misc-unused-using-decls-cxx17.cpp @@ -1,4 +1,4 @@ -// RUN: %check_clang_tidy -std=c++17-or-later %s misc-unused-using-decls %t -- -- -fno-delayed-template-parsing -isystem %S/Inputs/ +// RUN: %check_clang_tidy -std=c++17-or-later %s misc-unused-using-decls %t -- --fix-notes -- -fno-delayed-template-parsing -isystem %S/Inputs/ namespace ns { diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc-unused-using-decls.cpp b/clang-tools-extra/test/clang-tidy/checkers/misc-unused-using-decls.cpp index 9511160bc2f7b71a688f7c595082d3aa05fd0505..297649e0abcee6ce5ffd92b1749aad6dc3b5fd79 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/misc-unused-using-decls.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/misc-unused-using-decls.cpp @@ -1,5 +1,4 @@ -// RUN: %check_clang_tidy %s misc-unused-using-decls %t -- -- -fno-delayed-template-parsing -isystem %S/Inputs/ - +// RUN: %check_clang_tidy %s misc-unused-using-decls %t -- --fix-notes -- -fno-delayed-template-parsing -isystem %S/Inputs/ // ----- Definitions ----- template class vector {}; diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize-loop-convert-basic.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize-loop-convert-basic.cpp index 06b1b739632f0dca35452f06c3cb230efd4b1477..b2cd0e2ab513af3279f9a5599f1f09be31cb6b75 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/modernize-loop-convert-basic.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize-loop-convert-basic.cpp @@ -95,6 +95,33 @@ void f() { // CHECK-MESSAGES: :[[@LINE-3]]:3: warning: use range-based for loop instead // CHECK-FIXES: for (auto & Tea : Teas) // CHECK-FIXES-NEXT: Tea.g(); + + for (int I = 0; N > I; ++I) { + printf("Fibonacci number %d has address %p\n", Arr[I], &Arr[I]); + Sum += Arr[I] + 2; + } + // CHECK-MESSAGES: :[[@LINE-4]]:3: warning: use range-based for loop instead + // CHECK-FIXES: for (int & I : Arr) + // CHECK-FIXES-NEXT: printf("Fibonacci number %d has address %p\n", I, &I); + // CHECK-FIXES-NEXT: Sum += I + 2; + + for (int I = 0; N != I; ++I) { + printf("Fibonacci number %d has address %p\n", Arr[I], &Arr[I]); + Sum += Arr[I] + 2; + } + // CHECK-MESSAGES: :[[@LINE-4]]:3: warning: use range-based for loop instead + // CHECK-FIXES: for (int & I : Arr) + // CHECK-FIXES-NEXT: printf("Fibonacci number %d has address %p\n", I, &I); + // CHECK-FIXES-NEXT: Sum += I + 2; + + for (int I = 0; I != N; ++I) { + printf("Fibonacci number %d has address %p\n", Arr[I], &Arr[I]); + Sum += Arr[I] + 2; + } + // CHECK-MESSAGES: :[[@LINE-4]]:3: warning: use range-based for loop instead + // CHECK-FIXES: for (int & I : Arr) + // CHECK-FIXES-NEXT: printf("Fibonacci number %d has address %p\n", I, &I); + // CHECK-FIXES-NEXT: Sum += I + 2; } const int *constArray() { @@ -589,6 +616,33 @@ void f() { // CHECK-FIXES: for (int I : *Cv) // CHECK-FIXES-NEXT: printf("Fibonacci number is %d\n", I); // CHECK-FIXES-NEXT: Sum += I + 2; + + for (int I = 0, E = V.size(); E > I; ++I) { + printf("Fibonacci number is %d\n", V[I]); + Sum += V[I] + 2; + } + // CHECK-MESSAGES: :[[@LINE-4]]:3: warning: use range-based for loop instead + // CHECK-FIXES: for (int I : V) + // CHECK-FIXES-NEXT: printf("Fibonacci number is %d\n", I); + // CHECK-FIXES-NEXT: Sum += I + 2; + + for (int I = 0, E = V.size(); I != E; ++I) { + printf("Fibonacci number is %d\n", V[I]); + Sum += V[I] + 2; + } + // CHECK-MESSAGES: :[[@LINE-4]]:3: warning: use range-based for loop instead + // CHECK-FIXES: for (int I : V) + // CHECK-FIXES-NEXT: printf("Fibonacci number is %d\n", I); + // CHECK-FIXES-NEXT: Sum += I + 2; + + for (int I = 0, E = V.size(); E != I; ++I) { + printf("Fibonacci number is %d\n", V[I]); + Sum += V[I] + 2; + } + // CHECK-MESSAGES: :[[@LINE-4]]:3: warning: use range-based for loop instead + // CHECK-FIXES: for (int I : V) + // CHECK-FIXES-NEXT: printf("Fibonacci number is %d\n", I); + // CHECK-FIXES-NEXT: Sum += I + 2; } // Ensure that 'const auto &' is used with containers of non-trivial types. diff --git a/clang-tools-extra/test/clang-tidy/checkers/performance-for-range-copy.cpp b/clang-tools-extra/test/clang-tidy/checkers/performance-for-range-copy.cpp index e22650e10198a09f8443e9d871ade1d6f17286eb..07b5116dfb1480225a77599489a0fd03f36c2842 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/performance-for-range-copy.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/performance-for-range-copy.cpp @@ -60,13 +60,13 @@ void negativeConstReference() { void negativeUserDefinedConversion() { Convertible C[0]; - for (const S &S1 : C) { + for (const S S1 : C) { } } void negativeImplicitConstructorConversion() { ConstructorConvertible C[0]; - for (const S &S1 : C) { + for (const S S1 : C) { } } diff --git a/clang-tools-extra/test/clang-tidy/checkers/portability-simd-intrinsics-ppc.cpp b/clang-tools-extra/test/clang-tidy/checkers/portability-simd-intrinsics-ppc.cpp index 963379141a1371af9448d8c17b8591a1829d1c5e..5c03d39f736a1f0aa6c0554c5160f1b46ec8cbe3 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/portability-simd-intrinsics-ppc.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/portability-simd-intrinsics-ppc.cpp @@ -2,7 +2,10 @@ // RUN: -config='{CheckOptions: [ \ // RUN: {key: portability-simd-intrinsics.Suggest, value: 1} \ // RUN: ]}' -- -target ppc64le -maltivec -// FIXME: Fix the checker to work in C++20 mode. +// RUN: %check_clang_tidy -std=c++20-or-later %s portability-simd-intrinsics -check-suffix=CXX20 %t -- \ +// RUN: -config='{CheckOptions: [ \ +// RUN: {key: portability-simd-intrinsics.Suggest, value: 1} \ +// RUN: ]}' -- -target ppc64le -maltivec vector int vec_add(vector int, vector int); @@ -10,5 +13,6 @@ void PPC() { vector int i0, i1; vec_add(i0, i1); -// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: 'vec_add' can be replaced by operator+ on std::experimental::simd objects [portability-simd-intrinsics] + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: 'vec_add' can be replaced by operator+ on std::experimental::simd objects [portability-simd-intrinsics] + // CHECK-MESSAGES-CXX20: :[[@LINE-2]]:3: warning: 'vec_add' can be replaced by operator+ on std::simd objects [portability-simd-intrinsics] } diff --git a/clang-tools-extra/test/clang-tidy/checkers/portability-simd-intrinsics-x86.cpp b/clang-tools-extra/test/clang-tidy/checkers/portability-simd-intrinsics-x86.cpp index 24c9a82c5c42b01b841ee5616b0c9076a832ec36..8d71593c8d048bc0eef9b36323ab1c655a60c56e 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/portability-simd-intrinsics-x86.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/portability-simd-intrinsics-x86.cpp @@ -2,7 +2,10 @@ // RUN: -config='{CheckOptions: [ \ // RUN: {key: portability-simd-intrinsics.Suggest, value: 1} \ // RUN: ]}' -- -target x86_64 -// FIXME: Fix the checker to work in C++20 mode. +// RUN: %check_clang_tidy -std=c++20-or-later %s portability-simd-intrinsics -check-suffix=CXX20 %t -- \ +// RUN: -config='{CheckOptions: [ \ +// RUN: {key: portability-simd-intrinsics.Suggest, value: 1} \ +// RUN: ]}' -- -target x86_64 typedef long long __m128i __attribute__((vector_size(16))); typedef double __m256 __attribute__((vector_size(32))); @@ -18,7 +21,8 @@ void X86() { __m256 d0; _mm_add_epi32(i0, i1); -// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: '_mm_add_epi32' can be replaced by operator+ on std::experimental::simd objects [portability-simd-intrinsics] + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: '_mm_add_epi32' can be replaced by operator+ on std::experimental::simd objects [portability-simd-intrinsics] + // CHECK-MESSAGES-CXX20: :[[@LINE-2]]:3: warning: '_mm_add_epi32' can be replaced by operator+ on std::simd objects [portability-simd-intrinsics] d0 = _mm256_load_pd(0); _mm256_store_pd(0, d0); diff --git a/clang-tools-extra/test/clang-tidy/checkers/readability-function-cognitive-complexity-flags.cpp b/clang-tools-extra/test/clang-tidy/checkers/readability-function-cognitive-complexity-flags.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c52e7bcf1adccc622a67c5843763f3a28ba3312b --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/readability-function-cognitive-complexity-flags.cpp @@ -0,0 +1,42 @@ +// RUN: %check_clang_tidy %s readability-function-cognitive-complexity %t -- \ +// RUN: -config='{CheckOptions: \ +// RUN: [{key: readability-function-cognitive-complexity.Threshold, \ +// RUN: value: 0}, \ +// RUN: {key: readability-function-cognitive-complexity.DescribeBasicIncrements, \ +// RUN: value: "false"} ]}' +// RUN: %check_clang_tidy -check-suffix=THRESHOLD5 %s readability-function-cognitive-complexity %t -- \ +// RUN: -config='{CheckOptions: \ +// RUN: [{key: readability-function-cognitive-complexity.Threshold, \ +// RUN: value: 5}, \ +// RUN: {key: readability-function-cognitive-complexity.DescribeBasicIncrements, \ +// RUN: value: "false"} ]}' + +void func_of_complexity_4() { + // CHECK-NOTES: :[[@LINE-1]]:6: warning: function 'func_of_complexity_4' has cognitive complexity of 4 (threshold 0) [readability-function-cognitive-complexity] + if (1) { + if (1) { + } + } + if (1) { + } +} + +#define MacroOfComplexity10 \ + if (1) { \ + if (1) { \ + if (1) { \ + if (1) { \ + } \ + } \ + } \ + } + +void function_with_macro() { + // CHECK-NOTES: :[[@LINE-1]]:6: warning: function 'function_with_macro' has cognitive complexity of 11 (threshold 0) [readability-function-cognitive-complexity] + // CHECK-NOTES-THRESHOLD5: :[[@LINE-2]]:6: warning: function 'function_with_macro' has cognitive complexity of 11 (threshold 5) [readability-function-cognitive-complexity] + + MacroOfComplexity10; + + if (1) { + } +} diff --git a/clang-tools-extra/test/clang-tidy/checkers/readability-inconsistent-declaration-parameter-name.cpp b/clang-tools-extra/test/clang-tidy/checkers/readability-inconsistent-declaration-parameter-name.cpp index f00ebfa896a5f9899b30bb4119e29c086e032f92..982243255dd01a6d5e1ca7bb2d9e0aa31010f415 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/readability-inconsistent-declaration-parameter-name.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/readability-inconsistent-declaration-parameter-name.cpp @@ -1,4 +1,4 @@ -// RUN: %check_clang_tidy %s readability-inconsistent-declaration-parameter-name %t -- -- -fno-delayed-template-parsing +// RUN: %check_clang_tidy %s readability-inconsistent-declaration-parameter-name %t -- --fix-notes -- -fno-delayed-template-parsing void consistentFunction(int a, int b, int c); void consistentFunction(int a, int b, int c); diff --git a/clang-tools-extra/test/clang-tidy/checkers/readability-redundant-smartptr-get.cpp b/clang-tools-extra/test/clang-tidy/checkers/readability-redundant-smartptr-get.cpp index 51d98796265a00b1a8e96e45d07f19783483cab3..01f12b6bfe6ea0b060cac8dabb74fef75b5981ab 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/readability-redundant-smartptr-get.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/readability-redundant-smartptr-get.cpp @@ -168,6 +168,42 @@ void Positive() { // CHECK-FIXES: if (NULL == x); } +template +void testTemplate() { + T().get()->Do(); +} + +template +void testTemplate2() { + std::unique_ptr up; + up.get()->Do(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: redundant get() call + // CHECK-FIXES: up->Do(); +} + +void instantiate() { + testTemplate(); + testTemplate>(); + testTemplate(); + + testTemplate2(); +} + +struct S { + + void foo() { + m_up.get()->Do(); + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: redundant get() call + // CHECK-FIXES: m_up->Do(); + m_bp.get()->Do(); + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: redundant get() call + // CHECK-FIXES: m_bp->Do(); + } + + std::unique_ptr m_up; + BarPtr m_bp; +}; + #define MACRO(p) p.get() void Negative() { diff --git a/clang-tools-extra/test/clang-tidy/checkers/readability-uniqueptr-delete-release.cpp b/clang-tools-extra/test/clang-tidy/checkers/readability-uniqueptr-delete-release.cpp index bd51bc62dba821e194af51f15bd1de63383dc4e6..3ae66239e3fa0dc4b1f430da07e2567365b47a18 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/readability-uniqueptr-delete-release.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/readability-uniqueptr-delete-release.cpp @@ -1,5 +1,6 @@ -// RUN: %check_clang_tidy %s readability-uniqueptr-delete-release %t - +// RUN: %check_clang_tidy %s readability-uniqueptr-delete-release %t -check-suffix=NULLPTR +// RUN: %check_clang_tidy %s readability-uniqueptr-delete-release %t -check-suffix=RESET -config='{ \ +// RUN: CheckOptions: [{key: readability-uniqueptr-delete-release.PreferResetCall, value: true}]}' namespace std { template struct default_delete {}; @@ -13,6 +14,9 @@ class unique_ptr { template unique_ptr(unique_ptr&&); T* release(); + void reset(T *P = nullptr); + T &operator*() const; + T *operator->() const; }; } // namespace std @@ -21,22 +25,62 @@ std::unique_ptr& ReturnsAUnique(); void Positives() { std::unique_ptr P; delete P.release(); - // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: prefer '= nullptr' to 'delete x.release()' to reset unique_ptr<> objects [readability-uniqueptr-delete-release] - // CHECK-FIXES: {{^}} P = nullptr; + // CHECK-MESSAGES-NULLPTR: :[[@LINE-1]]:3: warning: prefer '= nullptr' to reset 'unique_ptr<>' objects + // CHECK-MESSAGES-RESET: :[[@LINE-2]]:3: warning: prefer 'reset()' to reset 'unique_ptr<>' objects + // CHECK-FIXES-NULLPTR: {{^}} P = nullptr; + // CHECK-FIXES-RESET: {{^}} P.reset(); auto P2 = P; delete P2.release(); - // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: prefer '= nullptr' to 'delete x.release()' to reset unique_ptr<> objects [readability-uniqueptr-delete-release] - // CHECK-FIXES: {{^}} P2 = nullptr; + // CHECK-MESSAGES-NULLPTR: :[[@LINE-1]]:3: warning: prefer '= nullptr' to reset 'unique_ptr<>' objects + // CHECK-MESSAGES-RESET: :[[@LINE-2]]:3: warning: prefer 'reset()' to reset 'unique_ptr<>' objects + // CHECK-FIXES-NULLPTR: {{^}} P2 = nullptr; + // CHECK-FIXES-RESET: {{^}} P2.reset(); + + delete (P2.release()); + // CHECK-MESSAGES-NULLPTR: :[[@LINE-1]]:3: warning: prefer '= nullptr' + // CHECK-MESSAGES-RESET: :[[@LINE-2]]:3: warning: prefer 'reset()' + // CHECK-FIXES-NULLPTR: {{^}} (P2 = nullptr); + // CHECK-FIXES-RESET: {{^}} (P2.reset()); std::unique_ptr Array[20]; delete Array[4].release(); - // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: prefer '= nullptr' to 'delete - // CHECK-FIXES: {{^}} Array[4] = nullptr; + // CHECK-MESSAGES-NULLPTR: :[[@LINE-1]]:3: warning: prefer '= nullptr' + // CHECK-MESSAGES-RESET: :[[@LINE-2]]:3: warning: prefer 'reset()' + // CHECK-FIXES-NULLPTR: {{^}} Array[4] = nullptr; + // CHECK-FIXES-RESET: {{^}} Array[4].reset(); delete ReturnsAUnique().release(); - // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: prefer '= nullptr' to 'delete - // CHECK-FIXES: {{^}} ReturnsAUnique() = nullptr; + // CHECK-MESSAGES-NULLPTR: :[[@LINE-1]]:3: warning: prefer '= nullptr' + // CHECK-MESSAGES-RESET: :[[@LINE-2]]:3: warning: prefer 'reset()' + // CHECK-FIXES-NULLPTR: {{^}} ReturnsAUnique() = nullptr; + // CHECK-FIXES-RESET: {{^}} ReturnsAUnique().reset(); + + std::unique_ptr *P3(&P); + delete P3->release(); + // CHECK-MESSAGES-NULLPTR: :[[@LINE-1]]:3: warning: prefer '= nullptr' + // CHECK-MESSAGES-RESET: :[[@LINE-2]]:3: warning: prefer 'reset()' + // CHECK-FIXES-NULLPTR: {{^}} *P3 = nullptr; + // CHECK-FIXES-RESET: {{^}} P3->reset(); + + std::unique_ptr> P4; + delete (*P4).release(); + // CHECK-MESSAGES-NULLPTR: :[[@LINE-1]]:3: warning: prefer '= nullptr' + // CHECK-MESSAGES-RESET: :[[@LINE-2]]:3: warning: prefer 'reset()' + // CHECK-FIXES-NULLPTR: {{^}} (*P4) = nullptr; + // CHECK-FIXES-RESET: {{^}} (*P4).reset(); + + delete P4->release(); + // CHECK-MESSAGES-NULLPTR: :[[@LINE-1]]:3: warning: prefer '= nullptr' + // CHECK-MESSAGES-RESET: :[[@LINE-2]]:3: warning: prefer 'reset()' + // CHECK-FIXES-NULLPTR: {{^}} *P4 = nullptr; + // CHECK-FIXES-RESET: {{^}} P4->reset(); + + delete (P4)->release(); + // CHECK-MESSAGES-NULLPTR: :[[@LINE-1]]:3: warning: prefer '= nullptr' + // CHECK-MESSAGES-RESET: :[[@LINE-2]]:3: warning: prefer 'reset()' + // CHECK-FIXES-NULLPTR: {{^}} *(P4) = nullptr; + // CHECK-FIXES-RESET: {{^}} (P4)->reset(); } struct NotDefaultDeleter {}; @@ -51,6 +95,9 @@ void Negatives() { NotUniquePtr P2; delete P2.release(); + + // We don't trigger on bound member function calls. + delete (P2.release)(); } template diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/alternative-fixes.cpp b/clang-tools-extra/test/clang-tidy/infrastructure/alternative-fixes.cpp index d5cee68d9b18896095eb5c911fa91447d5a1dbc1..9498a726078bfa1f4562f1e3387e4a080f2b403d 100644 --- a/clang-tools-extra/test/clang-tidy/infrastructure/alternative-fixes.cpp +++ b/clang-tools-extra/test/clang-tidy/infrastructure/alternative-fixes.cpp @@ -1,9 +1,10 @@ -// RUN: %check_clang_tidy %s "llvm-namespace-comment,clang-diagnostic-*" %t +// RUN: %check_clang_tidy %s "llvm-namespace-comment,clang-diagnostic-*" %t -- --fix-notes void foo(int a) { if (a = 1) { - // CHECK-NOTES: [[@LINE-1]]:9: warning: using the result of an assignment as a condition without parentheses [clang-diagnostic-parentheses] - // CHECK-NOTES: [[@LINE-2]]:9: note: place parentheses around the assignment to silence this warning - // CHECK-NOTES: [[@LINE-3]]:9: note: use '==' to turn this assignment into an equality comparison - // CHECK-FIXES: if ((a = 1)) { + // CHECK-NOTES: [[@LINE-1]]:9: warning: using the result of an assignment as a condition without parentheses [clang-diagnostic-parentheses] + // CHECK-NOTES: [[@LINE-2]]:9: note: place parentheses around the assignment to silence this warning + // CHECK-NOTES: [[@LINE-3]]:9: note: use '==' to turn this assignment into an equality comparison + // As we have 2 conflicting fixes in notes, don't apply any fix. + // CHECK-FIXES: if (a = 1) { } } diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/clean-up-code.cpp b/clang-tools-extra/test/clang-tidy/infrastructure/clean-up-code.cpp index 15873ed168dcecc192edff5a6fb722d859516960..bb2230803cea9b1ef2a10d1abc7efa0b4593fcf7 100644 --- a/clang-tools-extra/test/clang-tidy/infrastructure/clean-up-code.cpp +++ b/clang-tools-extra/test/clang-tidy/infrastructure/clean-up-code.cpp @@ -1,6 +1,6 @@ -// RUN: %check_clang_tidy %s misc-unused-using-decls %t -// RUN: %check_clang_tidy %s misc-unused-using-decls %t -- -format-style=none -- -// RUN: %check_clang_tidy %s misc-unused-using-decls %t -- -format-style=llvm -- +// RUN: %check_clang_tidy %s misc-unused-using-decls %t -- --fix-notes +// RUN: %check_clang_tidy %s misc-unused-using-decls %t -- --fix-notes -format-style=none -- +// RUN: %check_clang_tidy %s misc-unused-using-decls %t -- --fix-notes -format-style=llvm -- namespace a { class A {}; } namespace b { using a::A; diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/nolint-plugin.cpp b/clang-tools-extra/test/clang-tidy/infrastructure/nolint-plugin.cpp deleted file mode 100644 index d10c166207491878dc695368ffbc7cbfb77798a6..0000000000000000000000000000000000000000 --- a/clang-tools-extra/test/clang-tidy/infrastructure/nolint-plugin.cpp +++ /dev/null @@ -1,50 +0,0 @@ -// REQUIRES: static-analyzer, libclang_include_clang_tools_extra -// RUN: c-index-test -test-load-source-reparse 2 all %s -Xclang -add-plugin -Xclang clang-tidy -Xclang -plugin-arg-clang-tidy -Xclang -checks='-*,google-explicit-constructor,clang-diagnostic-unused-variable,clang-analyzer-core.UndefinedBinaryOperatorResult' -Wunused-variable -I%S/Inputs/nolint 2>&1 | FileCheck %s - -#include "trigger_warning.h" -void I(int& Out) { - int In; - A1(In, Out); -} -// CHECK-NOT: trigger_warning.h:{{.*}} warning -// CHECK-NOT: :[[@LINE-4]]:{{.*}} note - -class A { A(int i); }; -// CHECK-DAG: :[[@LINE-1]]:11: warning: single-argument constructors must be marked explicit - -class B { B(int i); }; // NOLINT - -class C { C(int i); }; // NOLINT(for-some-other-check) -// CHECK-DAG: :[[@LINE-1]]:11: warning: single-argument constructors must be marked explicit - -class C1 { C1(int i); }; // NOLINT(*) - -class C2 { C2(int i); }; // NOLINT(not-closed-bracket-is-treated-as-skip-all - -class C3 { C3(int i); }; // NOLINT(google-explicit-constructor) - -class C4 { C4(int i); }; // NOLINT(some-check, google-explicit-constructor) - -class C5 { C5(int i); }; // NOLINT without-brackets-skip-all, another-check - -void f() { - int i; -// CHECK-DAG: :[[@LINE-1]]:7: warning: unused variable 'i' [-Wunused-variable] -// 31:7: warning: unused variable 'i' [-Wunused-variable] -// int j; // NOLINT -// int k; // NOLINT(clang-diagnostic-unused-variable) -} - -#define MACRO(X) class X { X(int i); }; -MACRO(D) -// CHECK-DAG: :[[@LINE-1]]:7: warning: single-argument constructors must be marked explicit -MACRO(E) // NOLINT - -#define MACRO_NOARG class F { F(int i); }; -MACRO_NOARG // NOLINT - -#define MACRO_NOLINT class G { G(int i); }; // NOLINT -MACRO_NOLINT - -#define DOUBLE_MACRO MACRO(H) // NOLINT -DOUBLE_MACRO diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/nolintnextline-plugin.cpp b/clang-tools-extra/test/clang-tidy/infrastructure/nolintnextline-plugin.cpp deleted file mode 100644 index 4835f4c52a45d7b9052f91dec290a4139b8439c8..0000000000000000000000000000000000000000 --- a/clang-tools-extra/test/clang-tidy/infrastructure/nolintnextline-plugin.cpp +++ /dev/null @@ -1,49 +0,0 @@ -// REQUIRES: libclang_include_clang_tools_extra -// RUN: c-index-test -test-load-source-reparse 2 all %s -Xclang -add-plugin -Xclang clang-tidy -Xclang -plugin-arg-clang-tidy -Xclang -checks='-*,google-explicit-constructor' 2>&1 | FileCheck %s - -class A { A(int i); }; -// CHECK: :[[@LINE-1]]:11: warning: single-argument constructors must be marked explicit - -// NOLINTNEXTLINE -class B { B(int i); }; - -// NOLINTNEXTLINE(for-some-other-check) -class C { C(int i); }; -// CHECK: :[[@LINE-1]]:11: warning: single-argument constructors must be marked explicit - -// NOLINTNEXTLINE(*) -class C1 { C1(int i); }; - -// NOLINTNEXTLINE(not-closed-bracket-is-treated-as-skip-all -class C2 { C2(int i); }; - -// NOLINTNEXTLINE(google-explicit-constructor) -class C3 { C3(int i); }; - -// NOLINTNEXTLINE(some-check, google-explicit-constructor) -class C4 { C4(int i); }; - -// NOLINTNEXTLINE without-brackets-skip-all, another-check -class C5 { C5(int i); }; - - -// NOLINTNEXTLINE - -class D { D(int i); }; -// CHECK: :[[@LINE-1]]:11: warning: single-argument constructors must be marked explicit - -// NOLINTNEXTLINE -// -class E { E(int i); }; -// CHECK: :[[@LINE-1]]:11: warning: single-argument constructors must be marked explicit - -#define MACRO(X) class X { X(int i); }; -MACRO(F) -// CHECK: :[[@LINE-1]]:7: warning: single-argument constructors must be marked explicit -// NOLINTNEXTLINE -MACRO(G) - -#define MACRO_NOARG class H { H(int i); }; -// NOLINTNEXTLINE -MACRO_NOARG - diff --git a/clang-tools-extra/test/lit.site.cfg.py.in b/clang-tools-extra/test/lit.site.cfg.py.in index 7eef661b85fd18059c5d8c62aa2566ad5ea83ddd..f8300c1dd39dcea9418d80871842d6ef9c900f93 100644 --- a/clang-tools-extra/test/lit.site.cfg.py.in +++ b/clang-tools-extra/test/lit.site.cfg.py.in @@ -11,7 +11,6 @@ config.clang_libs_dir = "@SHLIBDIR@" config.python_executable = "@Python3_EXECUTABLE@" config.target_triple = "@TARGET_TRIPLE@" config.clang_tidy_staticanalyzer = @CLANG_TIDY_ENABLE_STATIC_ANALYZER@ -config.libclang_include_clang_tools_extra = @LIBCLANG_INCLUDE_CLANG_TOOLS_EXTRA@ # Support substitution of the tools and libs dirs with user parameters. This is # used when we can't determine the tool dir at configuration time. diff --git a/clang-tools-extra/test/pp-trace/pp-trace-include.cpp b/clang-tools-extra/test/pp-trace/pp-trace-include.cpp index ba6ad112dfa46e74776ba7bf74c0b966270b9c5f..d5578cf16f59ef40e3bf9aeb5ee5e8a15f01a0b1 100644 --- a/clang-tools-extra/test/pp-trace/pp-trace-include.cpp +++ b/clang-tools-extra/test/pp-trace/pp-trace-include.cpp @@ -39,7 +39,8 @@ // CHECK-NEXT: Reason: EnterFile // CHECK-NEXT: FileType: C_User // CHECK-NEXT: PrevFID: (invalid) -// CHECK-NEXT: - Callback: FileChanged +// CHECK: - Callback: MacroDefined +// CHECK: - Callback: FileChanged // CHECK-NEXT: Loc: ":1:1" // CHECK-NEXT: Reason: ExitFile // CHECK-NEXT: FileType: C_User diff --git a/clang-tools-extra/test/pp-trace/pp-trace-macro.cpp b/clang-tools-extra/test/pp-trace/pp-trace-macro.cpp index 47f9e1c4ff6a43c790f18357bed44948e1da4cd0..1d85607e86b7fffb526a5e677dd5c05bb5347384 100644 --- a/clang-tools-extra/test/pp-trace/pp-trace-macro.cpp +++ b/clang-tools-extra/test/pp-trace/pp-trace-macro.cpp @@ -31,6 +31,7 @@ X // CHECK: MacroNameTok: __STDC_UTF_32__ // CHECK-NEXT: MacroDirective: MD_Define // CHECK: - Callback: MacroDefined +// CHECK: - Callback: MacroDefined // CHECK-NEXT: MacroNameTok: MACRO // CHECK-NEXT: MacroDirective: MD_Define // CHECK-NEXT: - Callback: MacroExpands diff --git a/clang-tools-extra/unittests/clang-tidy/ClangTidyOptionsTest.cpp b/clang-tools-extra/unittests/clang-tidy/ClangTidyOptionsTest.cpp index 6447f34661e9b15f6e689dc9f3e6d718ef496763..c4fb8bd7b75631b490cf4cddd5aa3bb28bbedafa 100644 --- a/clang-tools-extra/unittests/clang-tidy/ClangTidyOptionsTest.cpp +++ b/clang-tools-extra/unittests/clang-tidy/ClangTidyOptionsTest.cpp @@ -190,6 +190,7 @@ MATCHER_P(DiagRange, P, "") { return arg.Range && *arg.Range == P; } using ::testing::AllOf; using ::testing::ElementsAre; +using ::testing::UnorderedElementsAre; TEST(ParseConfiguration, CollectDiags) { DiagCollecter Collector; @@ -243,7 +244,6 @@ public: return Options.getLocalOrGlobal(std::forward(Arguments)...); } }; -} // namespace #define CHECK_VAL(Value, Expected) \ do { \ @@ -252,17 +252,22 @@ public: EXPECT_EQ(*Item, Expected); \ } while (false) -#define CHECK_ERROR(Value, ErrorType, ExpectedMessage) \ - do { \ - auto Item = Value; \ - ASSERT_FALSE(Item); \ - ASSERT_TRUE(Item.errorIsA()); \ - ASSERT_FALSE(llvm::handleErrors( \ - Item.takeError(), [&](const ErrorType &Err) -> llvm::Error { \ - EXPECT_EQ(Err.message(), ExpectedMessage); \ - return llvm::Error::success(); \ - })); \ - } while (false) +MATCHER_P(ToolDiagMessage, M, "") { return arg.Message.Message == M; } +MATCHER_P(ToolDiagLevel, L, "") { return arg.DiagLevel == L; } + +} // namespace + +} // namespace test + +static constexpr auto Warning = tooling::Diagnostic::Warning; +static constexpr auto Error = tooling::Diagnostic::Error; + +static void PrintTo(const ClangTidyError &Err, ::std::ostream *OS) { + *OS << (Err.DiagLevel == Error ? "error: " : "warning: ") + << Err.Message.Message; +} + +namespace test { TEST(CheckOptionsValidation, MissingOptions) { ClangTidyOptions Options; @@ -273,21 +278,21 @@ TEST(CheckOptionsValidation, MissingOptions) { &DiagConsumer, false); Context.setDiagnosticsEngine(&DE); TestCheck TestCheck(&Context); - CHECK_ERROR(TestCheck.getLocal("Opt"), MissingOptionError, - "option not found 'test.Opt'"); + EXPECT_FALSE(TestCheck.getLocal("Opt").hasValue()); EXPECT_EQ(TestCheck.getLocal("Opt", "Unknown"), "Unknown"); + // Missing options aren't errors. + EXPECT_TRUE(DiagConsumer.take().empty()); } TEST(CheckOptionsValidation, ValidIntOptions) { ClangTidyOptions Options; auto &CheckOptions = Options.CheckOptions; - CheckOptions["test.IntExpected1"] = "1"; - CheckOptions["test.IntExpected2"] = "1WithMore"; - CheckOptions["test.IntExpected3"] = "NoInt"; - CheckOptions["GlobalIntExpected1"] = "1"; - CheckOptions["GlobalIntExpected2"] = "NoInt"; - CheckOptions["test.DefaultedIntInvalid"] = "NoInt"; + CheckOptions["test.IntExpected"] = "1"; + CheckOptions["test.IntInvalid1"] = "1WithMore"; + CheckOptions["test.IntInvalid2"] = "NoInt"; + CheckOptions["GlobalIntExpected"] = "1"; CheckOptions["GlobalIntInvalid"] = "NoInt"; + CheckOptions["test.DefaultedIntInvalid"] = "NoInt"; CheckOptions["test.BoolITrueValue"] = "1"; CheckOptions["test.BoolIFalseValue"] = "0"; CheckOptions["test.BoolTrueValue"] = "true"; @@ -304,22 +309,12 @@ TEST(CheckOptionsValidation, ValidIntOptions) { Context.setDiagnosticsEngine(&DE); TestCheck TestCheck(&Context); -#define CHECK_ERROR_INT(Name, Expected) \ - CHECK_ERROR(Name, UnparseableIntegerOptionError, Expected) - - CHECK_VAL(TestCheck.getIntLocal("IntExpected1"), 1); - CHECK_VAL(TestCheck.getIntGlobal("GlobalIntExpected1"), 1); - CHECK_ERROR_INT(TestCheck.getIntLocal("IntExpected2"), - "invalid configuration value '1WithMore' for option " - "'test.IntExpected2'; expected an integer value"); - CHECK_ERROR_INT(TestCheck.getIntLocal("IntExpected3"), - "invalid configuration value 'NoInt' for option " - "'test.IntExpected3'; expected an integer value"); - CHECK_ERROR_INT(TestCheck.getIntGlobal("GlobalIntExpected2"), - "invalid configuration value 'NoInt' for option " - "'GlobalIntExpected2'; expected an integer value"); + CHECK_VAL(TestCheck.getIntLocal("IntExpected"), 1); + CHECK_VAL(TestCheck.getIntGlobal("GlobalIntExpected"), 1); + EXPECT_FALSE(TestCheck.getIntLocal("IntInvalid1").hasValue()); + EXPECT_FALSE(TestCheck.getIntLocal("IntInvalid2").hasValue()); + EXPECT_FALSE(TestCheck.getIntGlobal("GlobalIntInvalid").hasValue()); ASSERT_EQ(TestCheck.getIntLocal("DefaultedIntInvalid", 1), 1); - ASSERT_EQ(TestCheck.getIntGlobal("GlobalIntInvalid", 1), 1); CHECK_VAL(TestCheck.getIntLocal("BoolITrueValue"), true); CHECK_VAL(TestCheck.getIntLocal("BoolIFalseValue"), false); @@ -327,11 +322,31 @@ TEST(CheckOptionsValidation, ValidIntOptions) { CHECK_VAL(TestCheck.getIntLocal("BoolFalseValue"), false); CHECK_VAL(TestCheck.getIntLocal("BoolTrueShort"), true); CHECK_VAL(TestCheck.getIntLocal("BoolFalseShort"), false); - CHECK_ERROR_INT(TestCheck.getIntLocal("BoolUnparseable"), - "invalid configuration value 'Nothing' for option " - "'test.BoolUnparseable'; expected a bool"); - -#undef CHECK_ERROR_INT + EXPECT_FALSE(TestCheck.getIntLocal("BoolUnparseable").hasValue()); + + EXPECT_THAT( + DiagConsumer.take(), + UnorderedElementsAre( + AllOf(ToolDiagMessage( + "invalid configuration value '1WithMore' for option " + "'test.IntInvalid1'; expected an integer"), + ToolDiagLevel(Warning)), + AllOf( + ToolDiagMessage("invalid configuration value 'NoInt' for option " + "'test.IntInvalid2'; expected an integer"), + ToolDiagLevel(Warning)), + AllOf( + ToolDiagMessage("invalid configuration value 'NoInt' for option " + "'GlobalIntInvalid'; expected an integer"), + ToolDiagLevel(Warning)), + AllOf(ToolDiagMessage( + "invalid configuration value 'NoInt' for option " + "'test.DefaultedIntInvalid'; expected an integer"), + ToolDiagLevel(Warning)), + AllOf(ToolDiagMessage( + "invalid configuration value 'Nothing' for option " + "'test.BoolUnparseable'; expected a bool"), + ToolDiagLevel(Warning)))); } TEST(ValidConfiguration, ValidEnumOptions) { @@ -350,11 +365,12 @@ TEST(ValidConfiguration, ValidEnumOptions) { ClangTidyContext Context(std::make_unique( ClangTidyGlobalOptions(), Options)); + ClangTidyDiagnosticConsumer DiagConsumer(Context); + DiagnosticsEngine DE(new DiagnosticIDs(), new DiagnosticOptions, + &DiagConsumer, false); + Context.setDiagnosticsEngine(&DE); TestCheck TestCheck(&Context); -#define CHECK_ERROR_ENUM(Name, Expected) \ - CHECK_ERROR(Name, UnparseableEnumOptionError, Expected) - CHECK_VAL(TestCheck.getIntLocal("Valid"), Colours::Red); CHECK_VAL(TestCheck.getIntGlobal("GlobalValid"), Colours::Violet); @@ -364,30 +380,42 @@ TEST(ValidConfiguration, ValidEnumOptions) { CHECK_VAL(TestCheck.getIntGlobal("GlobalValidWrongCase", /*IgnoreCase*/ true), Colours::Violet); - CHECK_ERROR_ENUM(TestCheck.getIntLocal("Invalid"), - "invalid configuration value " - "'Scarlet' for option 'test.Invalid'"); - CHECK_ERROR_ENUM(TestCheck.getIntLocal("ValidWrongCase"), - "invalid configuration value 'rED' for option " - "'test.ValidWrongCase'; did you mean 'Red'?"); - CHECK_ERROR_ENUM(TestCheck.getIntLocal("NearMiss"), - "invalid configuration value 'Oragne' for option " - "'test.NearMiss'; did you mean 'Orange'?"); - CHECK_ERROR_ENUM(TestCheck.getIntGlobal("GlobalInvalid"), - "invalid configuration value " - "'Purple' for option 'GlobalInvalid'"); - CHECK_ERROR_ENUM(TestCheck.getIntGlobal("GlobalValidWrongCase"), - "invalid configuration value 'vIOLET' for option " - "'GlobalValidWrongCase'; did you mean 'Violet'?"); - CHECK_ERROR_ENUM(TestCheck.getIntGlobal("GlobalNearMiss"), - "invalid configuration value 'Yelow' for option " - "'GlobalNearMiss'; did you mean 'Yellow'?"); - -#undef CHECK_ERROR_ENUM + + EXPECT_FALSE(TestCheck.getIntLocal("ValidWrongCase").hasValue()); + EXPECT_FALSE(TestCheck.getIntLocal("NearMiss").hasValue()); + EXPECT_FALSE(TestCheck.getIntGlobal("GlobalInvalid").hasValue()); + EXPECT_FALSE( + TestCheck.getIntGlobal("GlobalValidWrongCase").hasValue()); + EXPECT_FALSE(TestCheck.getIntGlobal("GlobalNearMiss").hasValue()); + + EXPECT_FALSE(TestCheck.getIntLocal("Invalid").hasValue()); + EXPECT_THAT( + DiagConsumer.take(), + UnorderedElementsAre( + AllOf(ToolDiagMessage("invalid configuration value " + "'Scarlet' for option 'test.Invalid'"), + ToolDiagLevel(Warning)), + AllOf(ToolDiagMessage("invalid configuration value 'rED' for option " + "'test.ValidWrongCase'; did you mean 'Red'?"), + ToolDiagLevel(Warning)), + AllOf( + ToolDiagMessage("invalid configuration value 'Oragne' for option " + "'test.NearMiss'; did you mean 'Orange'?"), + ToolDiagLevel(Warning)), + AllOf(ToolDiagMessage("invalid configuration value " + "'Purple' for option 'GlobalInvalid'"), + ToolDiagLevel(Warning)), + AllOf( + ToolDiagMessage("invalid configuration value 'vIOLET' for option " + "'GlobalValidWrongCase'; did you mean 'Violet'?"), + ToolDiagLevel(Warning)), + AllOf( + ToolDiagMessage("invalid configuration value 'Yelow' for option " + "'GlobalNearMiss'; did you mean 'Yellow'?"), + ToolDiagLevel(Warning)))); } #undef CHECK_VAL -#undef CHECK_ERROR } // namespace test } // namespace tidy diff --git a/clang/CMakeLists.txt b/clang/CMakeLists.txt index 7e1e58fdc1381b015c221117fe7ca4fdc2b16ad4..4695dc8a46fff016318e549ecd17ab0aae778323 100644 --- a/clang/CMakeLists.txt +++ b/clang/CMakeLists.txt @@ -262,8 +262,6 @@ set(CLANG_DEFAULT_UNWINDLIB "" CACHE STRING if (CLANG_DEFAULT_UNWINDLIB STREQUAL "") if (CLANG_DEFAULT_RTLIB STREQUAL "libgcc") set (CLANG_DEFAULT_UNWINDLIB "libgcc" CACHE STRING "" FORCE) - elseif (CLANG_DEFAULT_RTLIBS STREQUAL "libunwind") - set (CLANG_DEFAULT_UNWINDLIB "none" CACHE STRING "" FORCE) endif() endif() @@ -273,7 +271,7 @@ if (NOT(CLANG_DEFAULT_UNWINDLIB STREQUAL "" OR CLANG_DEFAULT_UNWINDLIB STREQUAL "libunwind")) message(WARNING "Resetting default unwindlib to use platform default") set(CLANG_DEFAULT_UNWINDLIB "" CACHE STRING - "Default unwind library to use (\"none\" \"libgcc\" or \"libunwind\", empty for none)" FORCE) + "Default unwind library to use (\"none\" \"libgcc\" or \"libunwind\", empty to match runtime library.)" FORCE) endif() set(CLANG_DEFAULT_OBJCOPY "objcopy" CACHE STRING diff --git a/clang/cmake/caches/Fuchsia-stage2.cmake b/clang/cmake/caches/Fuchsia-stage2.cmake index 7f84f74d348f18968b17c7cac782ef1c66eb90ea..667e33c700d5f24970e3b65dfa986a3634530e60 100644 --- a/clang/cmake/caches/Fuchsia-stage2.cmake +++ b/clang/cmake/caches/Fuchsia-stage2.cmake @@ -4,7 +4,7 @@ set(LLVM_TARGETS_TO_BUILD X86;ARM;AArch64;RISCV CACHE STRING "") set(PACKAGE_VENDOR Fuchsia CACHE STRING "") -set(LLVM_ENABLE_PROJECTS "clang;clang-tools-extra;lld;llvm" CACHE STRING "") +set(LLVM_ENABLE_PROJECTS "clang;clang-tools-extra;lld;llvm;polly" CACHE STRING "") set(LLVM_ENABLE_RUNTIMES "compiler-rt;libcxx;libcxxabi;libunwind" CACHE STRING "") set(LLVM_ENABLE_BACKTRACES OFF CACHE BOOL "") diff --git a/clang/cmake/caches/Fuchsia.cmake b/clang/cmake/caches/Fuchsia.cmake index 8688b71ecc75340872b75ea75f4919241461f51a..2809f0192cdbb0e2dcf78231db50c2eecec7441e 100644 --- a/clang/cmake/caches/Fuchsia.cmake +++ b/clang/cmake/caches/Fuchsia.cmake @@ -4,7 +4,7 @@ set(LLVM_TARGETS_TO_BUILD X86;ARM;AArch64;RISCV CACHE STRING "") set(PACKAGE_VENDOR Fuchsia CACHE STRING "") -set(LLVM_ENABLE_PROJECTS "clang;clang-tools-extra;lld;llvm" CACHE STRING "") +set(LLVM_ENABLE_PROJECTS "clang;clang-tools-extra;lld;llvm;polly" CACHE STRING "") set(LLVM_ENABLE_BACKTRACES OFF CACHE BOOL "") set(LLVM_ENABLE_PER_TARGET_RUNTIME_DIR ON CACHE BOOL "") diff --git a/clang/docs/AutomaticReferenceCounting.rst b/clang/docs/AutomaticReferenceCounting.rst index c75ef025415bee566c4c2106c076b6310bd0d8fe..9b0b6b86eb11ea4d3481dc543aa92e086e91346d 100644 --- a/clang/docs/AutomaticReferenceCounting.rst +++ b/clang/docs/AutomaticReferenceCounting.rst @@ -1345,7 +1345,7 @@ or transferring them. Similar transfers of responsibility occur for ``__weak`` fields, but since both sides must use native ``__weak`` support to ensure -calling convention compatibililty, this transfer is always handled +calling convention compatibility, this transfer is always handled automatically by the compiler. .. admonition:: Rationale diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst index 7ae8ea913099ab6e9f84e4cb3ba2bdc2680db64c..bf28e0d8697427810f58bc89787f5bae42b7e68f 100644 --- a/clang/docs/ClangFormatStyleOptions.rst +++ b/clang/docs/ClangFormatStyleOptions.rst @@ -37,7 +37,7 @@ The configuration file can consist of several sections each having different ``Language:`` parameter denoting the programming language this section of the configuration is targeted at. See the description of the **Language** option below for the list of supported languages. The first section may have no -language set, it will set the default style options for all lanugages. +language set, it will set the default style options for all languages. Configuration sections for specific language will override options set in the default section. @@ -2210,14 +2210,16 @@ the configuration (without a prefix: ``Auto``). not use this in config files, etc. Use at your own risk. **FixNamespaceComments** (``bool``) - If ``true``, clang-format adds missing namespace end comments and - fixes invalid existing ones. + If ``true``, clang-format adds missing namespace end comments for + short namespaces and fixes invalid existing ones. Short ones are + controlled by "ShortNamespaceLines". .. code-block:: c++ true: false: namespace a { vs. namespace a { foo(); foo(); + bar(); bar(); } // namespace a } **ForEachMacros** (``std::vector``) @@ -2360,6 +2362,33 @@ the configuration (without a prefix: ``Auto``). ``ClassImpl.hpp`` would not have the main include file put on top before any other include. +**IndentAccessModifiers** (``bool``) + Specify whether access modifiers should have their own indentation level. + + When ``false``, access modifiers are indented (or outdented) relative to + the record members, respecting the ``AccessModifierOffset``. Record + members are indented one level below the record. + When ``true``, access modifiers get their own indentation level. As a + consequence, record members are always indented 2 levels below the record, + regardless of the access modifier presence. Value of the + ``AccessModifierOffset`` is ignored. + + .. code-block:: c++ + + false: true: + class C { vs. class C { + class D { class D { + void bar(); void bar(); + protected: protected: + D(); D(); + }; }; + public: public: + C(); C(); + }; }; + void foo() { void foo() { + return 1; return 1; + } } + **IndentCaseBlocks** (``bool``) Indent case label blocks one level from the case label. @@ -3013,43 +3042,72 @@ the configuration (without a prefix: ``Auto``). /* second veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongComment with plenty of * information */ +**ShortNamespaceLines** (``unsigned``) + The maximal number of unwrapped lines that a short namespace spans. + Defaults to 1. + + This determines the maximum length of short namespaces by counting + unwrapped lines (i.e. containing neither opening nor closing + namespace brace) and makes "FixNamespaceComments" omit adding + end comments for those. + + .. code-block:: c++ + + ShortNamespaceLines: 1 vs. ShortNamespaceLines: 0 + namespace a { namespace a { + int foo; int foo; + } } // namespace a + + ShortNamespaceLines: 1 vs. ShortNamespaceLines: 0 + namespace b { namespace b { + int foo; int foo; + int bar; int bar; + } // namespace b } // namespace b + **SortIncludes** (``SortIncludesOptions``) Controls if and how clang-format will sort ``#includes``. + If ``Never``, includes are never sorted. + If ``CaseInsensitive``, includes are sorted in an ASCIIbetical or case + insensitive fashion. + If ``CaseSensitive``, includes are sorted in an alphabetical or case + sensitive fashion. - Possible Values: + Possible values: - * ``SI_Never`` (in configuration ``Never``) + * ``SI_Never`` (in configuration: ``Never``) Includes are never sorted. .. code-block:: c++ - #include "B/A.h" - #include "A/B.h" - #include "a/b.h" - #include "A/b.h" - #include "B/a.h" + #include "B/A.h" + #include "A/B.h" + #include "a/b.h" + #include "A/b.h" + #include "B/a.h" - * ``SI_CaseInsensitive`` (in configuration ``CaseInsensitive``) - Includes are sorted in an ASCIIbetical or case insensitive fashion. + * ``SI_CaseSensitive`` (in configuration: ``CaseSensitive``) + Includes are sorted in an ASCIIbetical or case sensitive fashion. .. code-block:: c++ - #include "A/B.h" - #include "A/b.h" - #include "B/A.h" - #include "B/a.h" - #include "a/b.h" + #include "A/B.h" + #include "A/b.h" + #include "B/A.h" + #include "B/a.h" + #include "a/b.h" - * ``SI_CaseSensitive`` (in configuration ``CaseSensitive``) - Includes are sorted in an alphabetical or case sensitive fashion. + * ``SI_CaseInsensitive`` (in configuration: ``CaseInsensitive``) + Includes are sorted in an alphabetical or case insensitive fashion. .. code-block:: c++ - #include "A/B.h" - #include "A/b.h" - #include "a/b.h" - #include "B/A.h" - #include "B/a.h" + #include "A/B.h" + #include "A/b.h" + #include "a/b.h" + #include "B/A.h" + #include "B/a.h" + + **SortJavaStaticImport** (``SortJavaStaticImportOptions``) When sorting Java imports, by default static imports are placed before @@ -3655,7 +3713,7 @@ The result is: break; } if (condition) - do_somthing_completely_different(); + do_something_completely_different(); if (x == y) { diff --git a/clang/docs/ControlFlowIntegrity.rst b/clang/docs/ControlFlowIntegrity.rst index 3f6b3ca6cafbc4ece2821fea55b493790ac4417f..97074210aa4596c6920fdcb3371a93d915aa4466 100644 --- a/clang/docs/ControlFlowIntegrity.rst +++ b/clang/docs/ControlFlowIntegrity.rst @@ -283,7 +283,7 @@ for CFI. For example, this is necessary when a function's address is taken by assembly code and then called by CFI-checking C code. The ``__attribute__((cfi_canonical_jump_table))`` attribute may be used to make the jump table entry of a specific function canonical so that the external -code will end up taking a address for the function that will pass CFI checks. +code will end up taking an address for the function that will pass CFI checks. ``-fsanitize=cfi-icall`` and ``-fsanitize=function`` ---------------------------------------------------- diff --git a/clang/docs/ControlFlowIntegrityDesign.rst b/clang/docs/ControlFlowIntegrityDesign.rst index d04486ac48136e237c08ac04b57aa16427794016..2505066098f23d6cf555e5665d31812b42c61bda 100644 --- a/clang/docs/ControlFlowIntegrityDesign.rst +++ b/clang/docs/ControlFlowIntegrityDesign.rst @@ -718,7 +718,7 @@ General case ------------ For functions called multiple times a *return jump table* is constructed in the same manner as jump tables for indirect function calls (see above). -The correct jump table entry (or it's index) is passed by `CALL` to `f()` +The correct jump table entry (or its index) is passed by `CALL` to `f()` (as an extra argument) and then spilled to stack. The `RET` instruction is replaced with a load of the jump table entry, jump table range check, and `JMP` to the jump table entry. diff --git a/clang/docs/InternalsManual.rst b/clang/docs/InternalsManual.rst index ec018755f4915f8d5f7338542dc24075ceba4a30..32c8f2dad5aa69af5ed807bfa9130a7ff17068db 100644 --- a/clang/docs/InternalsManual.rst +++ b/clang/docs/InternalsManual.rst @@ -770,7 +770,7 @@ uses key paths, which are declared in two steps. First, a tablegen definition for the ``CompilerInvocation`` member is created by inheriting from ``KeyPathAndMacro``: -.. code-block:: +.. code-block:: text // Options.td @@ -861,7 +861,7 @@ information required for parsing or generating the command line argument. The key path defaults to ``false`` and is set to ``true`` when the flag is present on command line. -.. code-block:: +.. code-block:: text def fignore_exceptions : Flag<["-"], "fignore-exceptions">, Flags<[CC1Option]>, MarshallingInfoFlag>; @@ -871,7 +871,7 @@ present on command line. The key path defaults to ``true`` and is set to ``false`` when the flag is present on command line. -.. code-block:: +.. code-block:: text def fno_verbose_asm : Flag<["-"], "fno-verbose-asm">, Flags<[CC1Option]>, MarshallingInfoNegativeFlag>; @@ -883,7 +883,7 @@ boolean value that's statically unknown in the tablegen file). Then, the key path is set to the value associated with the flag that appears last on command line. -.. code-block:: +.. code-block:: text defm legacy_pass_manager : BoolOption<"f", "legacy-pass-manager", CodeGenOpts<"LegacyPassManager">, DefaultFalse, @@ -911,7 +911,7 @@ the positive and negative flag and their common help text suffix. The key path defaults to the specified string, or an empty one, if omitted. When the option appears on the command line, the argument value is simply copied. -.. code-block:: +.. code-block:: text def isysroot : JoinedOrSeparate<["-"], "isysroot">, Flags<[CC1Option]>, MarshallingInfoString, [{"/"}]>; @@ -922,7 +922,7 @@ The key path defaults to an empty ``std::vector``. Values specified with each appearance of the option on the command line are appended to the vector. -.. code-block:: +.. code-block:: text def frewrite_map_file : Separate<["-"], "frewrite-map-file">, Flags<[CC1Option]>, MarshallingInfoStringVector>; @@ -933,10 +933,10 @@ The key path defaults to the specified integer value, or ``0`` if omitted. When the option appears on the command line, its value gets parsed by ``llvm::APInt`` and the result is assigned to the key path on success. -.. code-block:: +.. code-block:: text def mstack_probe_size : Joined<["-"], "mstack-probe-size=">, Flags<[CC1Option]>, - MarshallingInfoStringInt, "4096">; + MarshallingInfoInt, "4096">; **Enumeration** @@ -950,7 +950,7 @@ same index is assigned to the key path (also correctly scoped). The number of comma-separated string values and elements of the array within ``NormalizedValues`` must match. -.. code-block:: +.. code-block:: text def mthread_model : Separate<["-"], "mthread-model">, Flags<[CC1Option]>, Values<"posix,single">, NormalizedValues<["POSIX", "Single"]>, @@ -970,7 +970,7 @@ annotation. Then, if any of the elements of ``ImpliedByAnyOf`` evaluate to true, the key path value is changed to the specified value or ``true`` if missing. Finally, the command line is parsed according to the primary annotation. -.. code-block:: +.. code-block:: text def fms_extensions : Flag<["-"], "fms-extensions">, Flags<[CC1Option]>, MarshallingInfoFlag>, @@ -981,7 +981,7 @@ Finally, the command line is parsed according to the primary annotation. The option is parsed only if the expression in ``ShouldParseIf`` evaluates to true. -.. code-block:: +.. code-block:: text def fopenmp_enable_irbuilder : Flag<["-"], "fopenmp-enable-irbuilder">, Flags<[CC1Option]>, MarshallingInfoFlag>, @@ -1854,7 +1854,7 @@ Because the same entity can be defined multiple times in different modules, it is also possible for there to be multiple definitions of (for instance) a ``CXXRecordDecl``, all of which describe a definition of the same class. In such a case, only one of those "definitions" is considered by Clang to be -the definiition of the class, and the others are treated as non-defining +the definition of the class, and the others are treated as non-defining declarations that happen to also contain member declarations. Corresponding members in each definition of such multiply-defined classes are identified either by redeclaration chains (if the members are ``Redeclarable``) diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst index 4b82b8a6dbbc3bd380f92ada3b911ca2f126e155..a906dc79e03bcfd678479954a47382cdd56b5931 100644 --- a/clang/docs/LanguageExtensions.rst +++ b/clang/docs/LanguageExtensions.rst @@ -132,7 +132,7 @@ macro returns a nonzero value based on the year and month in which the attribute was voted into the working draft. See `WG21 SD-6 `_ for the list of values returned for standards-based attributes. If the attribute -is not supported by the current compliation target, this macro evaluates to 0. +is not supported by the current compilation target, this macro evaluates to 0. It can be used like this: .. code-block:: c++ @@ -542,7 +542,7 @@ The behavior of ``__fp16`` is specified by the ARM C Language Extensions (`ACLE Clang uses the ``binary16`` format from IEEE 754-2008 for ``__fp16``, not the ARM alternative format. -``_Float16`` is an extended floating-point type. This means that, just like arithmetic on +``_Float16`` is an interchange floating-point type. This means that, just like arithmetic on ``float`` or ``double``, arithmetic on ``_Float16`` operands is formally performed in the ``_Float16`` type, so that e.g. the result of adding two ``_Float16`` values has type ``_Float16``. The behavior of ``_Float16`` is specified by ISO/IEC TS 18661-3:2015 @@ -1194,7 +1194,9 @@ The following type trait primitives are supported by Clang. Those traits marked * ``__is_sealed`` (Microsoft): Synonym for ``__is_final``. * ``__is_signed`` (C++, Embarcadero): - Returns false for enumeration types, and returns true for floating-point types. Note, before Clang 10, returned true for enumeration types if the underlying type was signed, and returned false for floating-point types. + Returns false for enumeration types, and returns true for floating-point + types. Note, before Clang 10, returned true for enumeration types if the + underlying type was signed, and returned false for floating-point types. * ``__is_standard_layout`` (C++, GNU, Microsoft, Embarcadero) * ``__is_trivial`` (C++, GNU, Microsoft, Embarcadero) * ``__is_trivially_assignable`` (C++, GNU, Microsoft) @@ -1202,10 +1204,9 @@ The following type trait primitives are supported by Clang. Those traits marked * ``__is_trivially_copyable`` (C++, GNU, Microsoft) * ``__is_trivially_destructible`` (C++, MSVC 2013) * ``__is_union`` (C++, GNU, Microsoft, Embarcadero) -* ``__is_unsigned`` (C++, Embarcadero) - Note that this currently returns true for enumeration types if the underlying - type is unsigned, in violation of the requirements for ``std::is_unsigned``. - This behavior is likely to change in a future version of Clang. +* ``__is_unsigned`` (C++, Embarcadero): + Returns false for enumeration types. Note, before Clang 13, returned true for + enumeration types if the underlying type was unsigned. * ``__is_void`` (C++, Embarcadero) * ``__is_volatile`` (C++, Embarcadero) * ``__reference_binds_to_temporary(T, U)`` (Clang): Determines whether a @@ -1784,7 +1785,7 @@ Extension Specification, section 1.2 This is not conformant behavior and it can only be used portably when the functions with variadic prototypes do not get generated in binary e.g. the -variadic prototype is used to spesify a function type with any number of +variadic prototype is used to specify a function type with any number of arguments in metaprogramming algorithms in C++ for OpenCL. This extensions can also be used when the kernel code is intended for targets @@ -2495,7 +2496,7 @@ guarantees not to call any external functions. See LLVM IR `llvm.memcpy.inline `_ intrinsic for more information. -This is useful to implement a custom version of ``memcpy``, implemement a +This is useful to implement a custom version of ``memcpy``, implement a ``libc`` memcpy or work around the absence of a ``libc``. Note that the `size` argument must be a compile time constant. diff --git a/clang/docs/OpenMPSupport.rst b/clang/docs/OpenMPSupport.rst index f0d8e8741304d19b0b432c3f05d24a2c760112e9..e66d9a64a93f6eaa4e542548d981f532d13bb628 100644 --- a/clang/docs/OpenMPSupport.rst +++ b/clang/docs/OpenMPSupport.rst @@ -298,9 +298,9 @@ want to help with the implementation. +------------------------------+--------------------------------------------------------------+--------------------------+-----------------------------------------------------------------------+ | device extension | assorted routines for querying interoperable properties | :none:`unclaimed` | | +------------------------------+--------------------------------------------------------------+--------------------------+-----------------------------------------------------------------------+ -| loop extension | Loop tiling transformation | :part:`worked on` | D76342 | +| loop extension | Loop tiling transformation | :good:`done` | D76342 | +------------------------------+--------------------------------------------------------------+--------------------------+-----------------------------------------------------------------------+ -| loop extension | Loop unrolling transformation | :none:`unclaimed` | | +| loop extension | Loop unrolling transformation | :none:`worked on` | | +------------------------------+--------------------------------------------------------------+--------------------------+-----------------------------------------------------------------------+ | loop extension | 'reproducible'/'unconstrained' modifiers in 'order' clause | :none:`unclaimed` | | +------------------------------+--------------------------------------------------------------+--------------------------+-----------------------------------------------------------------------+ diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 26b380407c0b3567fad942ee3b055212d9ec212c..c78445b9be6f1417fd8e95bf39c5742df6cd2c15 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -151,6 +151,10 @@ Build System Changes These are major changes to the build system that have happened since the 12.0.0 release of Clang. Users of the build system should adjust accordingly. +- The option ``LIBCLANG_INCLUDE_CLANG_TOOLS_EXTRA`` no longer exists. There were + two releases with that flag forced off, and no uses were added that forced it + on. The recommended replacement is clangd. + - ... AST Matchers @@ -166,8 +170,8 @@ clang-format - Option ``SortIncludes`` has been updated from a ``bool`` to an ``enum`` with backwards compatibility. In addition to the previous - ``true``/``false`` states (now ``CaseInsensitive``/``Never``), a third - state has been added (``CaseSensitive``) which causes an alphabetical sort + ``true``/``false`` states (now ``CaseSensitive``/``Never``), a third + state has been added (``CaseInsensitive``) which causes an alphabetical sort with case used as a tie-breaker. .. code-block:: c++ @@ -179,14 +183,14 @@ clang-format #include "A/b.h" #include "B/a.h" - // CaseInsensitive (previously true) + // CaseSensitive (previously true) #include "A/B.h" #include "A/b.h" #include "B/A.h" #include "B/a.h" #include "a/b.h" - // CaseSensitive + // CaseInsensitive #include "A/B.h" #include "A/b.h" #include "a/b.h" @@ -196,6 +200,15 @@ clang-format - ``BasedOnStyle: InheritParentConfig`` allows to use the ``.clang-format`` of the parent directories to overwrite only parts of it. +- Option ``IndentAccessModifiers`` has been added to be able to give access + modifiers their own indentation level inside records. + +- Option ``ShortNamespaceLines`` has been added to give better control + over ``FixNamespaceComments`` when determining a namespace length. + +- Support for Whitesmiths has been improved, with fixes for ``namespace`` blocks + and ``case`` blocks and labels. + libclang -------- diff --git a/clang/docs/SanitizerCoverage.rst b/clang/docs/SanitizerCoverage.rst index 4c8ef7509cb57dcd5a13f295ebd1fe0d9f99dfd6..485a73d273aedb40f3c387fdd48cf294fecc5fc0 100644 --- a/clang/docs/SanitizerCoverage.rst +++ b/clang/docs/SanitizerCoverage.rst @@ -319,7 +319,7 @@ It is sometimes useful to tell SanitizerCoverage to instrument only a subset of functions in your target. With ``-fsanitize-coverage-allowlist=allowlist.txt`` and ``-fsanitize-coverage-blocklist=blocklist.txt``, -you can specify such a subset through the combination of a allowlist and a blocklist. +you can specify such a subset through the combination of an allowlist and a blocklist. SanitizerCoverage will only instrument functions that satisfy two conditions. First, the function should belong to a source file with a path that is both allowlisted diff --git a/clang/docs/UsersManual.rst b/clang/docs/UsersManual.rst index 6f593cab8f1a08d5131f80f80d839b0da3205f12..28de4e3aac6f74cc240dc10618f00ff2bb2afd2c 100644 --- a/clang/docs/UsersManual.rst +++ b/clang/docs/UsersManual.rst @@ -764,12 +764,12 @@ compilations steps. is sent to. If it specifies a regular file, the data are saved to this file in CSV format: -.. code-block:: console + .. code-block:: console - $ clang -fproc-stat-report=abc foo.c - $ cat abc - clang-11,"/tmp/foo-123456.o",92000,84000,87536 - ld,"a.out",900,8000,53568 + $ clang -fproc-stat-report=abc foo.c + $ cat abc + clang-11,"/tmp/foo-123456.o",92000,84000,87536 + ld,"a.out",900,8000,53568 The data on each row represent: @@ -780,19 +780,38 @@ compilations steps. * peak memory usage in Kb. It is possible to specify this option without any value. In this case statistics - is printed on standard output in human readable format: + are printed on standard output in human readable format: -.. code-block:: console + .. code-block:: console - $ clang -fproc-stat-report foo.c - clang-11: output=/tmp/foo-855a8e.o, total=68.000 ms, user=60.000 ms, mem=86920 Kb - ld: output=a.out, total=8.000 ms, user=4.000 ms, mem=52320 Kb + $ clang -fproc-stat-report foo.c + clang-11: output=/tmp/foo-855a8e.o, total=68.000 ms, user=60.000 ms, mem=86920 Kb + ld: output=a.out, total=8.000 ms, user=4.000 ms, mem=52320 Kb The report file specified in the option is locked for write, so this option can be used to collect statistics in parallel builds. The report file is not cleared, new data is appended to it, thus making posible to accumulate build statistics. + You can also use environment variables to control the process statistics reporting. + Setting ``CC_PRINT_PROC_STAT`` to ``1`` enables the feature, the report goes to + stdout in human readable format. + Setting ``CC_PRINT_PROC_STAT_FILE`` to a fully qualified file path makes it report + process statistics to the given file in the CSV format. Specifying a relative + path will likely lead to multiple files with the same name created in different + directories, since the path is relative to a changing working directory. + + These environment variables are handy when you need to request the statistics + report without changing your build scripts or alter the existing set of compiler + options. Note that ``-fproc-stat-report`` take precedence over ``CC_PRINT_PROC_STAT`` + and ``CC_PRINT_PROC_STAT_FILE``. + + .. code-block:: console + + $ export CC_PRINT_PROC_STAT=1 + $ export CC_PRINT_PROC_STAT_FILE=~/project-build-proc-stat.csv + $ make + Other Options ------------- Clang options that don't fit neatly into other categories. @@ -2241,14 +2260,14 @@ programs using the same instrumentation method as ``-fprofile-generate``. The resulted ``cs_code.prodata`` combines ``code.profdata`` and the profile generated from binary ``cs_code``. Profile ``cs_code.profata`` can be used by - ``-fprofile-use`` compilaton. + ``-fprofile-use`` compilation. .. code-block:: console $ clang++ -O2 -fprofile-use=cs_code.profdata The above command will read both profiles to the compiler at the identical - point of instrumenations. + point of instrumentations. .. option:: -fprofile-use[=] @@ -3504,7 +3523,8 @@ Execute ``clang-cl /?`` to see a list of supported options: /Gs Use stack probes (default) /Gs Set stack probe size (default 4096) /guard: Enable Control Flow Guard with /guard:cf, - or only the table with /guard:cf,nochecks + or only the table with /guard:cf,nochecks. + Enable EH Continuation Guard with /guard:ehcont /Gv Set __vectorcall as a default calling convention /Gw- Don't put each data item in its own section /Gw Put each data item in its own section diff --git a/clang/docs/analyzer/checkers.rst b/clang/docs/analyzer/checkers.rst index b47be97eef964ce0465c46e0dc00fdf907309f0d..9a74dffc1658dba5862cd1ebdf443850d8ec30e5 100644 --- a/clang/docs/analyzer/checkers.rst +++ b/clang/docs/analyzer/checkers.rst @@ -438,7 +438,7 @@ optin.cplusplus.UninitializedObject (C++) This checker reports uninitialized fields in objects created after a constructor call. It doesn't only find direct uninitialized fields, but rather makes a deep -inspection of the object, analyzing all of it's fields subfields. +inspection of the object, analyzing all of its fields' subfields. The checker regards inherited fields as direct fields, so one will receive warnings for uninitialized inherited data members as well. @@ -1476,6 +1476,9 @@ Reports similar pieces of code. return y; } +alpha.core +^^^^^^^^^^ + .. _alpha-core-BoolAssignment: alpha.core.BoolAssignment (ObjC) @@ -1488,9 +1491,6 @@ Warn about assigning non-{0,1} values to boolean variables. BOOL b = -1; // warn } -alpha.core -^^^^^^^^^^ - .. _alpha-core-C11Lock: alpha.core.C11Lock diff --git a/clang/docs/analyzer/developer-docs/IPA.rst b/clang/docs/analyzer/developer-docs/IPA.rst index 2e8fe37055b332096db383f6dd0c953e7655c9b2..c8a9a08bd2b9180146b62075d1542782b462fdfb 100644 --- a/clang/docs/analyzer/developer-docs/IPA.rst +++ b/clang/docs/analyzer/developer-docs/IPA.rst @@ -128,7 +128,7 @@ If the conditions are right for inlining, a CallEnter node is created and added to the analysis work list. The CallEnter node marks the change to a new LocationContext representing the called function, and its state includes the contents of the new stack frame. When the CallEnter node is actually processed, -its single successor will be a edge to the first CFG block in the function. +its single successor will be an edge to the first CFG block in the function. Exiting an inlined function is a bit more work, fortunately broken up into reasonable steps: diff --git a/clang/docs/tools/dump_ast_matchers.py b/clang/docs/tools/dump_ast_matchers.py index 18afbdd36c6e4ce652444ecbe1373868ef312bb1..2a26d10f7a04dacd075ca63685a9d63be734c11f 100755 --- a/clang/docs/tools/dump_ast_matchers.py +++ b/clang/docs/tools/dump_ast_matchers.py @@ -351,13 +351,17 @@ Flags can be combined with '|' example \"IgnoreCase | BasicRegex\" m = re.match( r"""^.*internal::VariadicFunction\s*<\s* - internal::PolymorphicMatcherWithParam1<[\S\s]+ - AST_POLYMORPHIC_SUPPORTED_TYPES\(([^)]*)\)>,\s*([^,]+), - \s*[^>]+>\s*([a-zA-Z]*);$""", + internal::PolymorphicMatcher<[\S\s]+ + AST_POLYMORPHIC_SUPPORTED_TYPES\(([^)]*)\),\s*(.*);$""", declaration, flags=re.X) if m: - results, arg, name = m.groups()[:3] + results, trailing = m.groups() + trailing, name = trailing.rsplit(">", 1) + name = name.strip() + trailing, _ = trailing.rsplit(",", 1) + _, arg = trailing.rsplit(",", 1) + arg = arg.strip() result_types = [r.strip() for r in results.split(',')] for result_type in result_types: diff --git a/clang/include/clang-c/Index.h b/clang/include/clang-c/Index.h index 6e599f17b974e85628178c81f2329aaac638d43b..b052501c6b24073339ff4938be55467337353f5b 100644 --- a/clang/include/clang-c/Index.h +++ b/clang/include/clang-c/Index.h @@ -2572,7 +2572,11 @@ enum CXCursorKind { */ CXCursor_OMPTileDirective = 288, - CXCursor_LastStmt = CXCursor_OMPTileDirective, + /** OpenMP canonical loop. + */ + CXCursor_OMPCanonicalLoop = 289, + + CXCursor_LastStmt = CXCursor_OMPCanonicalLoop, /** * Cursor that represents the translation unit itself. diff --git a/clang/include/clang/AST/Mangle.h b/clang/include/clang/AST/Mangle.h index 13b436cdca3e33ae0a8b5e819a36911f969b4a0f..dc620ec10ff9e9f6fc75e663fb86c0151d7f7686 100644 --- a/clang/include/clang/AST/Mangle.h +++ b/clang/include/clang/AST/Mangle.h @@ -110,6 +110,12 @@ public: virtual bool isDeviceMangleContext() const { return false; } virtual void setDeviceMangleContext(bool) {} + virtual bool isUniqueInternalLinkageDecl(const NamedDecl *ND) { + return false; + } + + virtual void needsUniqueInternalLinkageNames() { } + // FIXME: consider replacing raw_ostream & with something like SmallString &. void mangleName(GlobalDecl GD, raw_ostream &); virtual void mangleCXXName(GlobalDecl GD, raw_ostream &) = 0; diff --git a/clang/include/clang/AST/OpenMPClause.h b/clang/include/clang/AST/OpenMPClause.h index 9c6bbe6082fbd46a93b85f2a0035e8137966b9b9..958f2b9e0e6f2ab04a83ba25c1589f7d2c62b638 100644 --- a/clang/include/clang/AST/OpenMPClause.h +++ b/clang/include/clang/AST/OpenMPClause.h @@ -5355,14 +5355,14 @@ public: if (!(--RemainingLists)) { ++DeclCur; ++NumListsCur; - if (SupportsMapper) - ++MapperCur; RemainingLists = *NumListsCur; assert(RemainingLists && "No lists in the following declaration??"); } } ++ListSizeCur; + if (SupportsMapper) + ++MapperCur; return *this; } }; diff --git a/clang/include/clang/AST/RecursiveASTVisitor.h b/clang/include/clang/AST/RecursiveASTVisitor.h index 78878e2eb6c5b435c824d820d6c76f10715513af..8ec2c882a9f2f988d6d35cd3fffdbbd327ee438f 100644 --- a/clang/include/clang/AST/RecursiveASTVisitor.h +++ b/clang/include/clang/AST/RecursiveASTVisitor.h @@ -2787,6 +2787,14 @@ bool RecursiveASTVisitor::TraverseOMPExecutableDirective( return true; } +DEF_TRAVERSE_STMT(OMPCanonicalLoop, { + if (!getDerived().shouldVisitImplicitCode()) { + // Visit only the syntactical loop. + TRY_TO(TraverseStmt(S->getLoopStmt())); + ShouldVisitChildren = false; + } +}) + template bool RecursiveASTVisitor::TraverseOMPLoopDirective(OMPLoopDirective *S) { diff --git a/clang/include/clang/AST/Stmt.h b/clang/include/clang/AST/Stmt.h index c2e69a91e55d0986852b9171c6cad38fcd1bc6bf..2085904b7f01ede9c79442bdc15262c61363fb87 100644 --- a/clang/include/clang/AST/Stmt.h +++ b/clang/include/clang/AST/Stmt.h @@ -2119,7 +2119,7 @@ class SwitchStmt final : public Stmt, friend TrailingObjects; /// Points to a linked list of case and default statements. - SwitchCase *FirstCase; + SwitchCase *FirstCase = nullptr; // SwitchStmt is followed by several trailing objects, // some of which optional. Note that it would be more convenient to diff --git a/clang/include/clang/AST/StmtOpenMP.h b/clang/include/clang/AST/StmtOpenMP.h index 392fd82f51de696ec0fadd00e973bd51129c448a..32b7778aa487847cd5cc60677d1db1c26f772504 100644 --- a/clang/include/clang/AST/StmtOpenMP.h +++ b/clang/include/clang/AST/StmtOpenMP.h @@ -28,6 +28,238 @@ namespace clang { // AST classes for directives. //===----------------------------------------------------------------------===// +/// Representation of an OpenMP canonical loop. +/// +/// OpenMP 1.0 C/C++, section 2.4.1 for Construct; canonical-shape +/// OpenMP 2.0 C/C++, section 2.4.1 for Construct; canonical-shape +/// OpenMP 2.5, section 2.5.1 Loop Construct; canonical form +/// OpenMP 3.1, section 2.5.1 Loop Construct; canonical form +/// OpenMP 4.0, section 2.6 Canonical Loop Form +/// OpenMP 4.5, section 2.6 Canonical Loop Form +/// OpenMP 5.0, section 2.9.1 Canonical Loop Form +/// OpenMP 5.1, section 2.11.1 Canonical Loop Nest Form +/// +/// An OpenMP canonical loop is a for-statement or range-based for-statement +/// with additional requirements that ensure that the number of iterations is +/// known before entering the loop and allow skipping to an arbitrary iteration. +/// The OMPCanonicalLoop AST node wraps a ForStmt or CXXForRangeStmt that is +/// known to fulfill OpenMP's canonical loop requirements because of being +/// associated to an OMPLoopBasedDirective. That is, the general structure is: +/// +/// OMPLoopBasedDirective +/// [`- CapturedStmt ] +/// [ `- CapturedDecl] +/// ` OMPCanonicalLoop +/// `- ForStmt/CXXForRangeStmt +/// `- Stmt +/// +/// One or multiple CapturedStmt/CapturedDecl pairs may be inserted by some +/// directives such as OMPParallelForDirective, but others do not need them +/// (such as OMPTileDirective). In The OMPCanonicalLoop and +/// ForStmt/CXXForRangeStmt pair is repeated for loop associated with the +/// directive. A OMPCanonicalLoop must not appear in the AST unless associated +/// with a OMPLoopBasedDirective. In an imperfectly nested loop nest, the +/// OMPCanonicalLoop may also be wrapped in a CompoundStmt: +/// +/// [...] +/// ` OMPCanonicalLoop +/// `- ForStmt/CXXForRangeStmt +/// `- CompoundStmt +/// |- Leading in-between code (if any) +/// |- OMPCanonicalLoop +/// | `- ForStmt/CXXForRangeStmt +/// | `- ... +/// `- Trailing in-between code (if any) +/// +/// The leading/trailing in-between code must not itself be a OMPCanonicalLoop +/// to avoid confusion which loop belongs to the nesting. +/// +/// There are three different kinds of iteration variables for different +/// purposes: +/// * Loop user variable: The user-accessible variable with different value for +/// each iteration. +/// * Loop iteration variable: The variable used to identify a loop iteration; +/// for range-based for-statement, this is the hidden iterator '__begin'. For +/// other loops, it is identical to the loop user variable. Must be a +/// random-access iterator, pointer or integer type. +/// * Logical iteration counter: Normalized loop counter starting at 0 and +/// incrementing by one at each iteration. Allows abstracting over the type +/// of the loop iteration variable and is always an unsigned integer type +/// appropriate to represent the range of the loop iteration variable. Its +/// value corresponds to the logical iteration number in the OpenMP +/// specification. +/// +/// This AST node provides two captured statements: +/// * The distance function which computes the number of iterations. +/// * The loop user variable function that computes the loop user variable when +/// given a logical iteration number. +/// +/// These captured statements provide the link between C/C++ semantics and the +/// logical iteration counters used by the OpenMPIRBuilder which is +/// language-agnostic and therefore does not know e.g. how to advance a +/// random-access iterator. The OpenMPIRBuilder will use this information to +/// apply simd, workshare-loop, distribute, taskloop and loop directives to the +/// loop. For compatibility with the non-OpenMPIRBuilder codegen path, an +/// OMPCanonicalLoop can itself also be wrapped into the CapturedStmts of an +/// OMPLoopDirective and skipped when searching for the associated syntactical +/// loop. +/// +/// Example: +/// +/// std::vector Container{1,2,3}; +/// for (std::string Str : Container) +/// Body(Str); +/// +/// which is syntactic sugar for approximately: +/// +/// auto &&__range = Container; +/// auto __begin = std::begin(__range); +/// auto __end = std::end(__range); +/// for (; __begin != __end; ++__begin) { +/// std::String Str = *__begin; +/// Body(Str); +/// } +/// +/// In this example, the loop user variable is `Str`, the loop iteration +/// variable is `__begin` of type `std::vector::iterator` and the +/// logical iteration number type is `size_t` (unsigned version of +/// `std::vector::iterator::difference_type` aka `ptrdiff_t`). +/// Therefore, the distance function will be +/// +/// [&](size_t &Result) { Result = __end - __begin; } +/// +/// and the loop variable function is +/// +/// [&,__begin](std::vector::iterator &Result, size_t Logical) { +/// Result = __begin + Logical; +/// } +/// +/// The variable `__begin`, aka the loop iteration variable, is captured by +/// value because it is modified in the loop body, but both functions require +/// the initial value. The OpenMP specification explicitly leaves unspecified +/// when the loop expressions are evaluated such that a capture by reference is +/// sufficient. +class OMPCanonicalLoop : public Stmt { + friend class ASTStmtReader; + friend class ASTStmtWriter; + + /// Children of this AST node. + enum { + LOOP_STMT, + DISTANCE_FUNC, + LOOPVAR_FUNC, + LOOPVAR_REF, + LastSubStmt = LOOPVAR_REF + }; + +private: + /// This AST node's children. + Stmt *SubStmts[LastSubStmt + 1] = {}; + + OMPCanonicalLoop() : Stmt(StmtClass::OMPCanonicalLoopClass) {} + +public: + /// Create a new OMPCanonicalLoop. + static OMPCanonicalLoop *create(const ASTContext &Ctx, Stmt *LoopStmt, + CapturedStmt *DistanceFunc, + CapturedStmt *LoopVarFunc, + DeclRefExpr *LoopVarRef) { + OMPCanonicalLoop *S = new (Ctx) OMPCanonicalLoop(); + S->setLoopStmt(LoopStmt); + S->setDistanceFunc(DistanceFunc); + S->setLoopVarFunc(LoopVarFunc); + S->setLoopVarRef(LoopVarRef); + return S; + } + + /// Create an empty OMPCanonicalLoop for deserialization. + static OMPCanonicalLoop *createEmpty(const ASTContext &Ctx) { + return new (Ctx) OMPCanonicalLoop(); + } + + static bool classof(const Stmt *S) { + return S->getStmtClass() == StmtClass::OMPCanonicalLoopClass; + } + + SourceLocation getBeginLoc() const { return getLoopStmt()->getBeginLoc(); } + SourceLocation getEndLoc() const { return getLoopStmt()->getEndLoc(); } + + /// Return this AST node's children. + /// @{ + child_range children() { + return child_range(&SubStmts[0], &SubStmts[0] + LastSubStmt + 1); + } + const_child_range children() const { + return const_child_range(&SubStmts[0], &SubStmts[0] + LastSubStmt + 1); + } + /// @} + + /// The wrapped syntactic loop statement (ForStmt or CXXForRangeStmt). + /// @{ + Stmt *getLoopStmt() { return SubStmts[LOOP_STMT]; } + const Stmt *getLoopStmt() const { return SubStmts[LOOP_STMT]; } + void setLoopStmt(Stmt *S) { + assert((isa(S) || isa(S)) && + "Canonical loop must be a for loop (range-based or otherwise)"); + SubStmts[LOOP_STMT] = S; + } + /// @} + + /// The function that computes the number of loop iterations. Can be evaluated + /// before entering the loop but after the syntactical loop's init + /// statement(s). + /// + /// Function signature: void(LogicalTy &Result) + /// Any values necessary to compute the distance are captures of the closure. + /// @{ + CapturedStmt *getDistanceFunc() { + return cast(SubStmts[DISTANCE_FUNC]); + } + const CapturedStmt *getDistanceFunc() const { + return cast(SubStmts[DISTANCE_FUNC]); + } + void setDistanceFunc(CapturedStmt *S) { + assert(S && "Expected non-null captured statement"); + SubStmts[DISTANCE_FUNC] = S; + } + /// @} + + /// The function that computes the loop user variable from a logical iteration + /// counter. Can be evaluated as first statement in the loop. + /// + /// Function signature: void(LoopVarTy &Result, LogicalTy Number) + /// Any other values required to compute the loop user variable (such as start + /// value, step size) are captured by the closure. In particular, the initial + /// value of loop iteration variable is captured by value to be unaffected by + /// previous iterations. + /// @{ + CapturedStmt *getLoopVarFunc() { + return cast(SubStmts[LOOPVAR_FUNC]); + } + const CapturedStmt *getLoopVarFunc() const { + return cast(SubStmts[LOOPVAR_FUNC]); + } + void setLoopVarFunc(CapturedStmt *S) { + assert(S && "Expected non-null captured statement"); + SubStmts[LOOPVAR_FUNC] = S; + } + /// @} + + /// Reference to the loop user variable as accessed in the loop body. + /// @{ + DeclRefExpr *getLoopVarRef() { + return cast(SubStmts[LOOPVAR_REF]); + } + const DeclRefExpr *getLoopVarRef() const { + return cast(SubStmts[LOOPVAR_REF]); + } + void setLoopVarRef(DeclRefExpr *E) { + assert(E && "Expected non-null loop variable"); + SubStmts[LOOPVAR_REF] = E; + } + /// @} +}; + /// This is a basic class for representing single OpenMP executable /// directive. /// diff --git a/clang/include/clang/ASTMatchers/ASTMatchers.h b/clang/include/clang/ASTMatchers/ASTMatchers.h index b82929019f6ca8e0922222a78b11c6c2f873e4f4..885bc74ad3ea0b43b663f1db203566819ac4af25 100644 --- a/clang/include/clang/ASTMatchers/ASTMatchers.h +++ b/clang/include/clang/ASTMatchers/ASTMatchers.h @@ -835,26 +835,16 @@ traverse(TraversalKind TK, const internal::ArgumentAdaptingMatcherFuncAdaptor< ToTypes>>(TK, InnerMatcher); } -template