Skip to content

Commit f0f305a

Browse files
committed
[clang-tidy][readability-container-contains] Extend to any class with contains
This check will now work out of the box with other containers that have a `contains` method, such as `folly::F14` or Abseil containers. It will also work with strings, which are basically just weird containers. `std::string` and `std::string_view` will have a `contains` method starting with C++23. `llvm::StringRef` and `folly::StringPiece` are examples of existing implementations with a `contains` method.
1 parent bd840a4 commit f0f305a

File tree

5 files changed

+65
-38
lines changed

5 files changed

+65
-38
lines changed

clang-tools-extra/clang-tidy/readability/ContainerContainsCheck.cpp

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,26 +15,26 @@ using namespace clang::ast_matchers;
1515
namespace clang::tidy::readability {
1616

1717
void ContainerContainsCheck::registerMatchers(MatchFinder *Finder) {
18-
const auto SupportedContainers = hasType(
19-
hasUnqualifiedDesugaredType(recordType(hasDeclaration(cxxRecordDecl(
20-
hasAnyName("::std::set", "::std::unordered_set", "::std::map",
21-
"::std::unordered_map", "::std::multiset",
22-
"::std::unordered_multiset", "::std::multimap",
23-
"::std::unordered_multimap"))))));
18+
const auto HasContainsMethod = hasMethod(
19+
cxxMethodDecl(isConst(), returns(booleanType()), hasName("contains")));
20+
const auto ContainerWithContains = hasType(
21+
hasUnqualifiedDesugaredType(recordType(hasDeclaration(cxxRecordDecl(anyOf(
22+
HasContainsMethod, hasAnyBase(hasType(hasCanonicalType(hasDeclaration(
23+
cxxRecordDecl(HasContainsMethod)))))))))));
2424

2525
const auto CountCall =
26-
cxxMemberCallExpr(on(SupportedContainers),
26+
cxxMemberCallExpr(on(ContainerWithContains),
2727
callee(cxxMethodDecl(hasName("count"))),
2828
argumentCountIs(1))
2929
.bind("call");
3030

3131
const auto FindCall =
32-
cxxMemberCallExpr(on(SupportedContainers),
32+
cxxMemberCallExpr(on(ContainerWithContains),
3333
callee(cxxMethodDecl(hasName("find"))),
3434
argumentCountIs(1))
3535
.bind("call");
3636

37-
const auto EndCall = cxxMemberCallExpr(on(SupportedContainers),
37+
const auto EndCall = cxxMemberCallExpr(on(ContainerWithContains),
3838
callee(cxxMethodDecl(hasName("end"))),
3939
argumentCountIs(0));
4040

clang-tools-extra/clang-tidy/readability/ContainerContainsCheck.h

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,8 @@ class ContainerContainsCheck : public ClangTidyCheck {
2424
: ClangTidyCheck(Name, Context) {}
2525
void registerMatchers(ast_matchers::MatchFinder *Finder) final;
2626
void check(const ast_matchers::MatchFinder::MatchResult &Result) final;
27-
28-
protected:
2927
bool isLanguageVersionSupported(const LangOptions &LO) const final {
30-
return LO.CPlusPlus20;
28+
return LO.CPlusPlus;
3129
}
3230
};
3331

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,11 @@ Changes in existing checks
116116
<clang-tidy/checks/modernize/use-std-print>` check to support replacing
117117
member function calls too.
118118

119-
- Improved :doc:`readablility-implicit-bool-conversion
119+
- Improved :doc:`readability-container-contains
120+
<clang-tidy/checks/readability/container-contains>` check
121+
to let it work on any class that has a ``contains`` method.
122+
123+
- Improved :doc:`readability-implicit-bool-conversion
120124
<clang-tidy/checks/readability/implicit-bool-conversion>` check
121125
by adding the option `UseUpperCaseLiteralSuffix` to select the
122126
case of the literal suffix in fixes.

clang-tools-extra/docs/clang-tidy/checks/readability/container-contains.rst

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,31 @@
33
readability-container-contains
44
==============================
55

6-
Finds usages of ``container.count()`` and ``container.find() == container.end()`` which should be replaced by a call to the ``container.contains()`` method introduced in C++20.
6+
Finds usages of ``container.count()`` and
7+
``container.find() == container.end()`` which should be replaced by a call to
8+
the ``container.contains()`` method.
79

8-
Whether an element is contained inside a container should be checked with ``contains`` instead of ``count``/``find`` because ``contains`` conveys the intent more clearly. Furthermore, for containers which permit multiple entries per key (``multimap``, ``multiset``, ...), ``contains`` is more efficient than ``count`` because ``count`` has to do unnecessary additional work.
10+
Whether an element is contained inside a container should be checked with
11+
``contains`` instead of ``count``/``find`` because ``contains`` conveys the
12+
intent more clearly. Furthermore, for containers which permit multiple entries
13+
per key (``multimap``, ``multiset``, ...), ``contains`` is more efficient than
14+
``count`` because ``count`` has to do unnecessary additional work.
915

1016
Examples:
1117

12-
=========================================== ==============================
13-
Initial expression Result
14-
------------------------------------------- ------------------------------
15-
``myMap.find(x) == myMap.end()`` ``!myMap.contains(x)``
16-
``myMap.find(x) != myMap.end()`` ``myMap.contains(x)``
17-
``if (myMap.count(x))`` ``if (myMap.contains(x))``
18-
``bool exists = myMap.count(x)`` ``bool exists = myMap.contains(x)``
19-
``bool exists = myMap.count(x) > 0`` ``bool exists = myMap.contains(x)``
20-
``bool exists = myMap.count(x) >= 1`` ``bool exists = myMap.contains(x)``
21-
``bool missing = myMap.count(x) == 0`` ``bool missing = !myMap.contains(x)``
22-
=========================================== ==============================
18+
========================================= ==============================
19+
Initial expression Result
20+
----------------------------------------- ------------------------------
21+
``myMap.find(x) == myMap.end()`` ``!myMap.contains(x)``
22+
``myMap.find(x) != myMap.end()`` ``myMap.contains(x)``
23+
``if (myMap.count(x))`` ``if (myMap.contains(x))``
24+
``bool exists = myMap.count(x)`` ``bool exists = myMap.contains(x)``
25+
``bool exists = myMap.count(x) > 0`` ``bool exists = myMap.contains(x)``
26+
``bool exists = myMap.count(x) >= 1`` ``bool exists = myMap.contains(x)``
27+
``bool missing = myMap.count(x) == 0`` ``bool missing = !myMap.contains(x)``
28+
========================================= ==============================
2329

24-
This check applies to ``std::set``, ``std::unordered_set``, ``std::map``, ``std::unordered_map`` and the corresponding multi-key variants.
25-
It is only active for C++20 and later, as the ``contains`` method was only added in C++20.
30+
This check will apply to any class that has a ``contains`` method, notably
31+
including ``std::set``, ``std::unordered_set``, ``std::map``, and
32+
``std::unordered_map`` as of C++20, and ``std::string`` and ``std::string_view``
33+
as of C++23.

clang-tools-extra/test/clang-tidy/checkers/readability/container-contains.cpp

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ int testMacroExpansion(std::unordered_set<int> &MySet) {
240240
return 0;
241241
}
242242

243-
// The following map has the same interface like `std::map`.
243+
// The following map has the same interface as `std::map`.
244244
template <class Key, class T>
245245
struct CustomMap {
246246
unsigned count(const Key &K) const;
@@ -249,13 +249,30 @@ struct CustomMap {
249249
void *end();
250250
};
251251

252-
// The clang-tidy check is currently hard-coded against the `std::` containers
253-
// and hence won't annotate the following instance. We might change this in the
254-
// future and also detect the following case.
255-
void *testDifferentCheckTypes(CustomMap<int, int> &MyMap) {
256-
if (MyMap.count(0))
257-
// NO-WARNING.
258-
// CHECK-FIXES: if (MyMap.count(0))
259-
return nullptr;
260-
return MyMap.find(2);
252+
void testDifferentCheckTypes(CustomMap<int, int> &MyMap) {
253+
if (MyMap.count(0)) {};
254+
// CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use 'contains' to check for membership [readability-container-contains]
255+
// CHECK-FIXES: if (MyMap.contains(0)) {};
256+
257+
MyMap.find(0) != MyMap.end();
258+
// CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use 'contains' to check for membership [readability-container-contains]
259+
// CHECK-FIXES: MyMap.contains(0);
260+
}
261+
262+
struct MySubmap : public CustomMap<int, int> {};
263+
264+
void testSubclass(MySubmap& MyMap) {
265+
if (MyMap.count(0)) {};
266+
// CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use 'contains' to check for membership [readability-container-contains]
267+
// CHECK-FIXES: if (MyMap.contains(0)) {};
268+
}
269+
270+
using UsingMap = CustomMap<int, int>;
271+
struct MySubmap2 : public UsingMap {};
272+
using UsingMap2 = MySubmap2;
273+
274+
void testUsing(UsingMap2& MyMap) {
275+
if (MyMap.count(0)) {};
276+
// CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use 'contains' to check for membership [readability-container-contains]
277+
// CHECK-FIXES: if (MyMap.contains(0)) {};
261278
}

0 commit comments

Comments
 (0)