[clang-tools-extra] Add checks to convert std library iterator algorithms into c++20 or boost ranges (PR #97764)
Piotr Zegar via cfe-commits
cfe-commits at lists.llvm.org
Fri Jul 5 00:42:05 PDT 2024
================
@@ -0,0 +1,253 @@
+//===--- UseRangesCheck.cpp - clang-tidy ----------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "UseRangesCheck.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/ASTMatchers/ASTMatchersInternal.h"
+#include "clang/Basic/Diagnostic.h"
+#include "clang/Basic/SourceLocation.h"
+#include "clang/Basic/SourceManager.h"
+#include "clang/Lex/Lexer.h"
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/FoldingSet.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/raw_ostream.h"
+#include <cassert>
+#include <limits>
+#include <optional>
+#include <string>
+
+using namespace clang::ast_matchers;
+
+static constexpr const char BoundCall[] = "CallExpr";
+static constexpr const char FuncDecl[] = "FuncDecl";
+static constexpr const char ArgName[] = "ArgName";
+
+namespace clang::tidy::utils {
+
+static bool operator==(const UseRangesCheck::Replacer::Indexes &L,
+ const UseRangesCheck::Replacer::Indexes &R) {
+ return std::tie(L.BeginArg, L.EndArg, L.ReplaceArg) ==
+ std::tie(R.BeginArg, R.EndArg, R.ReplaceArg);
+}
+
+std::string
+getFullPrefix(ArrayRef<UseRangesCheck::Replacer::Indexes> Signature) {
+ std::string Output;
+ llvm::raw_string_ostream OS(Output);
+ for (auto Item : Signature) {
+ OS << Item.BeginArg << ":" << Item.EndArg << ":"
+ << (Item.ReplaceArg == Item.First ? '0' : '1');
+ }
+ return Output;
+}
+
+static llvm::hash_code hash_value(const UseRangesCheck::Replacer::Indexes &L) {
+ return llvm::hash_combine(L.BeginArg, L.EndArg, L.ReplaceArg);
+}
+
+namespace {
+
+AST_MATCHER(Expr, hasSideEffects) {
+ return Node.HasSideEffects(Finder->getASTContext());
+}
+
+AST_MATCHER_P(Expr, isEquivalentToBound, std::string, Other) {
+ llvm::FoldingSetNodeID NodeRepr;
+ Node.Profile(NodeRepr, Finder->getASTContext(), true);
+ return Builder->removeBindings(
+ [this, &Finder,
+ &NodeRepr](const ast_matchers::internal::BoundNodesMap &Nodes) {
+ auto BoundNode = Nodes.getNodeAs<Expr>(Other);
+ if (!BoundNode)
+ return true;
+ llvm::FoldingSetNodeID BoundRepr;
+ BoundNode->Profile(BoundRepr, Finder->getASTContext(), true);
+ return BoundRepr != NodeRepr;
+ });
+}
+} // namespace
+
+static auto makeMatcher(bool IsBegin, StringRef Prefix) {
+ auto Member =
+ IsBegin ? expr(unless(hasSideEffects())).bind((ArgName + Prefix).str())
+ : expr(isEquivalentToBound((ArgName + Prefix).str()));
+ return expr(
+ anyOf(cxxMemberCallExpr(
+ callee(cxxMethodDecl(IsBegin ? hasAnyName("begin", "cbegin")
+ : hasAnyName("end", "cend"))),
+ on(Member)),
+ callExpr(argumentCountIs(1), hasArgument(0, Member),
+ hasDeclaration(functionDecl(
+ IsBegin ? hasAnyName("::std::begin", "::std::cbegin")
+ : hasAnyName("::std::end", "::std::cend"))))));
+}
+static ast_matchers::internal::Matcher<CallExpr>
+makeMatcherPair(StringRef State,
+ const UseRangesCheck::Replacer::Indexes &Indexes) {
+ auto ArgPostfix = std::to_string(Indexes.BeginArg);
+ SmallString<64> ID = {BoundCall, State};
+ return callExpr(argumentCountAtLeast(
+ std::max(Indexes.BeginArg, Indexes.EndArg) + 1),
+ hasArgument(Indexes.BeginArg, makeMatcher(true, ArgPostfix)),
+ hasArgument(Indexes.EndArg, makeMatcher(false, ArgPostfix)))
+ .bind(ID);
+}
+
+void UseRangesCheck::registerMatchers(MatchFinder *Finder) {
+ Replaces = GetReplacerMap();
+ llvm::DenseSet<ArrayRef<ArrayRef<Replacer::Indexes>>> Seen;
+ for (auto I = Replaces.begin(), E = Replaces.end(); I != E; ++I) {
+ const auto &Replacer = I->getValue();
+ const auto &Signatures = Replacer->getReplacementSignatures();
+ if (Seen.contains(Signatures))
+ continue;
+ assert(!Signatures.empty() &&
+ llvm::all_of(Signatures, [](auto index) { return !index.empty(); }));
+ std::vector<StringRef> Names(1, I->getKey());
+ for (auto J = std::next(I); J != E; ++J) {
+ if (J->getValue()->getReplacementSignatures() == Signatures) {
+ Names.push_back(J->getKey());
+ }
+ }
+ std::vector<ast_matchers::internal::DynTypedMatcher> TotalMatchers;
+ // As we match on the first matched signature, we need to ensure that any
+ SmallVector<ArrayRef<Replacer::Indexes>> SigVec(Signatures);
+ llvm::sort(SigVec, [](auto &L, auto &R) { return R.size() < L.size(); });
+ for (const auto &Signature : SigVec) {
+ std::vector<ast_matchers::internal::DynTypedMatcher> Matchers;
+ for (const auto &ArgPair : Signature) {
+ Matchers.push_back(makeMatcherPair(getFullPrefix(Signature), ArgPair));
+ }
+ TotalMatchers.push_back(
+ ast_matchers::internal::DynTypedMatcher::constructVariadic(
+ ast_matchers::internal::DynTypedMatcher::VO_AllOf,
+ ASTNodeKind::getFromNodeKind<CallExpr>(), std::move(Matchers)));
+ }
+ Finder->addMatcher(
+ callExpr(
+ callee(functionDecl(hasAnyName(std::move(Names))).bind(FuncDecl)),
+ ast_matchers::internal::DynTypedMatcher::constructVariadic(
+ ast_matchers::internal::DynTypedMatcher::VO_AnyOf,
+ ASTNodeKind::getFromNodeKind<CallExpr>(),
+ std::move(TotalMatchers))
+ .convertTo<CallExpr>()),
+ this);
+ }
+}
+
+// Returns the proper token based end location of \p E.
+static SourceLocation exprLocEnd(const Expr *E, const ASTContext &Context) {
+ return Lexer::getLocForEndOfToken(
+ E->getEndLoc(), 0, Context.getSourceManager(), Context.getLangOpts());
+}
+
+static void removeFunctionArgs(const CallExpr &Call, ArrayRef<unsigned> Indexes,
+ llvm::SmallVectorImpl<FixItHint> &Output,
+ const ASTContext &Ctx) {
+ llvm::SmallVector<unsigned> Sorted(Indexes);
+ // Keep track of commas removed
+ llvm::SmallBitVector Commas(Call.getNumArgs());
+ // The first comma is actually the '(' which we can't remove
+ Commas[0] = true;
+ llvm::sort(Sorted);
+ for (auto Index : Sorted) {
+ const auto *Arg = Call.getArg(Index);
+ if (Commas[Index]) {
+ if (Index >= Commas.size()) {
+ Output.push_back(FixItHint::CreateRemoval(Arg->getSourceRange()));
+ } else {
+ // Remove the next comma
+ Commas[Index + 1] = true;
+ Output.push_back(
+ FixItHint::CreateRemoval(CharSourceRange::getTokenRange(
+ {Arg->getBeginLoc(),
+ exprLocEnd(Arg, Ctx).getLocWithOffset(1)})));
+ }
+ } else {
+ Output.push_back(FixItHint::CreateRemoval(CharSourceRange::getTokenRange(
+ Arg->getBeginLoc().getLocWithOffset(-1), Arg->getEndLoc())));
+ Commas[Index] = true;
+ }
+ }
+}
+
+void UseRangesCheck::check(const MatchFinder::MatchResult &Result) {
+ const auto *Function = Result.Nodes.getNodeAs<FunctionDecl>(FuncDecl);
+ auto QN = "::" + Function->getQualifiedNameAsString();
+ auto Iter = Replaces.find(QN);
+ assert(Iter != Replaces.end());
+ SmallString<64> Buffer;
+ for (const auto &Signature : Iter->getValue()->getReplacementSignatures()) {
+ Buffer.assign({BoundCall, getFullPrefix(Signature)});
+ const auto *Call = Result.Nodes.getNodeAs<CallExpr>(Buffer);
+ if (!Call)
+ continue;
+ auto Diag =
+ diag(Call->getBeginLoc(), "Use a ranges version of this algorithm");
----------------
PiotrZSL wrote:
"Use a ranges version of this algorithm" -> "use a ranges version of this algorithm"
https://github.com/llvm/llvm-project/pull/97764
More information about the cfe-commits
mailing list