[clang-tools-extra] [clang-tidy] support string::contains (PR #110351)

Tommy Chen via cfe-commits cfe-commits at lists.llvm.org
Sat Sep 28 00:44:43 PDT 2024


https://github.com/dl8sd11 created https://github.com/llvm/llvm-project/pull/110351

Starting from c++23, we can replace `std::string::find() == std::string::npos` with `std::string.contains()` . #109327

Currently, this is WIP because there are two limitations:

1. False positive: SubStr type is `const std::string&` which does not match `std::string::contains(basic_string_view)` type.

```
std::string SubStr;
  if (Str.find(SubStr) != std::string::npos) {};
```

2. Currently, the fixit for `std::string::find("test", 0)` is incorrect. 

>From b98e9a4d50d74c298096d2bd2d70ff4c0ef5c4a4 Mon Sep 17 00:00:00 2001
From: dl8sd11 <gcchen at google.com>
Date: Sat, 28 Sep 2024 07:37:50 +0000
Subject: [PATCH] [clang-tidy] support string::contains

---
 .../readability/ContainerContainsCheck.cpp    | 18 +++++--
 .../readability/container-contains.cpp        | 49 +++++++++++++++++++
 2 files changed, 63 insertions(+), 4 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/readability/ContainerContainsCheck.cpp b/clang-tools-extra/clang-tidy/readability/ContainerContainsCheck.cpp
index 05d4c87bc73cef..a5e5960eaa6a3c 100644
--- a/clang-tools-extra/clang-tidy/readability/ContainerContainsCheck.cpp
+++ b/clang-tools-extra/clang-tidy/readability/ContainerContainsCheck.cpp
@@ -9,6 +9,7 @@
 #include "ContainerContainsCheck.h"
 #include "clang/AST/ASTContext.h"
 #include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
 
 using namespace clang::ast_matchers;
 
@@ -32,7 +33,8 @@ void ContainerContainsCheck::registerMatchers(MatchFinder *Finder) {
 
   const auto FindCall =
       cxxMemberCallExpr(
-          argumentCountIs(1),
+          anyOf(argumentCountIs(1),
+                allOf(argumentCountIs(2), hasArgument(1, cxxDefaultArgExpr()))),
           callee(cxxMethodDecl(
               hasName("find"),
               hasParameter(0, hasType(hasUnqualifiedDesugaredType(
@@ -51,6 +53,12 @@ void ContainerContainsCheck::registerMatchers(MatchFinder *Finder) {
   const auto Literal0 = integerLiteral(equals(0));
   const auto Literal1 = integerLiteral(equals(1));
 
+  const auto StringLikeClass = cxxRecordDecl(
+      hasAnyName("::std::basic_string", "::std::basic_string_view",
+                 "::absl::string_view"));
+  const auto StringNpos = declRefExpr(
+      to(varDecl(hasName("npos"), hasDeclContext(StringLikeClass))));
+
   auto AddSimpleMatcher = [&](auto Matcher) {
     Finder->addMatcher(
         traverse(TK_IgnoreUnlessSpelledInSource, std::move(Matcher)), this);
@@ -94,12 +102,14 @@ void ContainerContainsCheck::registerMatchers(MatchFinder *Finder) {
       binaryOperator(hasLHS(Literal1), hasOperatorName(">"), hasRHS(CountCall))
           .bind("negativeComparison"));
 
-  // Find membership tests based on `find() == end()`.
+  // Find membership tests based on `find() == end() or find() == npos`.
   AddSimpleMatcher(
-      binaryOperator(hasOperatorName("!="), hasOperands(FindCall, EndCall))
+      binaryOperator(hasOperatorName("!="),
+                     hasOperands(FindCall, anyOf(EndCall, StringNpos)))
           .bind("positiveComparison"));
   AddSimpleMatcher(
-      binaryOperator(hasOperatorName("=="), hasOperands(FindCall, EndCall))
+      binaryOperator(hasOperatorName("=="),
+                     hasOperands(FindCall, anyOf(EndCall, StringNpos)))
           .bind("negativeComparison"));
 }
 
diff --git a/clang-tools-extra/test/clang-tidy/checkers/readability/container-contains.cpp b/clang-tools-extra/test/clang-tidy/checkers/readability/container-contains.cpp
index 9a9b233e07229b..69cc5c88479040 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/readability/container-contains.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/readability/container-contains.cpp
@@ -29,6 +29,43 @@ struct multimap {
   bool contains(const Key &K) const;
 };
 
+using size_t = decltype(sizeof(int));
+
+// Lightweight standin for std::string_view.
+template <typename C>
+class basic_string_view {
+public:
+  basic_string_view();
+  basic_string_view(const basic_string_view &);
+  basic_string_view(const C *);
+  ~basic_string_view();
+  int find(basic_string_view s, int pos = 0);
+  int find(const C *s, int pos = 0);
+  int find(const C *s, int pos, int n);
+  int find(char c, int pos = 0);
+  static constexpr size_t npos = -1;
+};
+typedef basic_string_view<char> string_view;
+
+// Lightweight standin for std::string.
+template <typename C>
+class basic_string {
+public:
+  basic_string();
+  basic_string(const basic_string &);
+  basic_string(const C *);
+  ~basic_string();
+  int find(basic_string s, int pos = 0);
+  int find(const C *s, int pos = 0);
+  int find(const C *s, int pos, int n);
+  int find(char c, int pos = 0);
+  bool contains(const C *s) const;
+  bool contains(C s) const;
+  bool contains(basic_string_view<C> s) const;
+  static constexpr size_t npos = -1;
+};
+typedef basic_string<char> string;
+
 } // namespace std
 
 // Check that we detect various common ways to check for membership
@@ -453,3 +490,15 @@ void testOperandPermutations(std::map<int, int>& Map) {
   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use 'contains' to check for membership [readability-container-contains]
   // CHECK-FIXES: if (!Map.contains(0)) {};
 }
+
+void testStringNops(std::string Str, std::string SubStr) {
+  if (Str.find("test") == std::string::npos) {};
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use 'contains' to check for membership [readability-container-contains]
+  // CHECK-FIXES: if (!Str.contains("test")) {};
+
+  if (Str.find('c') != std::string::npos) {};
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use 'contains' to check for membership [readability-container-contains]
+  // CHECK-FIXES: if (Str.contains('c')) {};
+  
+  if (Str.find(SubStr) != std::string::npos) {};
+}



More information about the cfe-commits mailing list