xref: /llvm-project/clang-tools-extra/test/clang-tidy/checkers/readability/container-contains.cpp (revision 1be4c9710bd09e2f56908ca6cee54cb80ca1774d)
1 // RUN: %check_clang_tidy -std=c++20-or-later %s readability-container-contains %t
2 
3 // Some *very* simplified versions of `map` etc.
4 namespace std {
5 
6 template <class Key, class T>
7 struct map {
8   unsigned count(const Key &K) const;
9   bool contains(const Key &K) const;
10   void *find(const Key &K);
11   void *end();
12 };
13 
14 template <class Key>
15 struct set {
16   unsigned count(const Key &K) const;
17   bool contains(const Key &K) const;
18 };
19 
20 template <class Key>
21 struct unordered_set {
22   unsigned count(const Key &K) const;
23   bool contains(const Key &K) const;
24 };
25 
26 template <class Key, class T>
27 struct multimap {
28   unsigned count(const Key &K) const;
29   bool contains(const Key &K) const;
30 };
31 
32 } // namespace std
33 
34 // Check that we detect various common ways to check for membership
35 int testDifferentCheckTypes(std::map<int, int> &MyMap) {
36   if (MyMap.count(0))
37     // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: use 'contains' to check for membership [readability-container-contains]
38     // CHECK-FIXES: if (MyMap.contains(0))
39     return 1;
40   bool C1 = MyMap.count(1);
41   // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: use 'contains' to check for membership [readability-container-contains]
42   // CHECK-FIXES: bool C1 = MyMap.contains(1);
43   auto C2 = static_cast<bool>(MyMap.count(1));
44   // CHECK-MESSAGES: :[[@LINE-1]]:37: warning: use 'contains' to check for membership [readability-container-contains]
45   // CHECK-FIXES: auto C2 = static_cast<bool>(MyMap.contains(1));
46   auto C3 = MyMap.count(2) != 0;
47   // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: use 'contains' to check for membership [readability-container-contains]
48   // CHECK-FIXES: auto C3 = MyMap.contains(2);
49   auto C4 = MyMap.count(3) > 0;
50   // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: use 'contains' to check for membership [readability-container-contains]
51   // CHECK-FIXES: auto C4 = MyMap.contains(3);
52   auto C5 = MyMap.count(4) >= 1;
53   // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: use 'contains' to check for membership [readability-container-contains]
54   // CHECK-FIXES: auto C5 = MyMap.contains(4);
55   auto C6 = MyMap.find(5) != MyMap.end();
56   // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: use 'contains' to check for membership [readability-container-contains]
57   // CHECK-FIXES: auto C6 = MyMap.contains(5);
58   return C1 + C2 + C3 + C4 + C5 + C6;
59 }
60 
61 // Check that we detect various common ways to check for non-membership
62 int testNegativeChecks(std::map<int, int> &MyMap) {
63   bool C1 = !MyMap.count(-1);
64   // CHECK-MESSAGES: :[[@LINE-1]]:20: warning: use 'contains' to check for membership [readability-container-contains]
65   // CHECK-FIXES: bool C1 = !MyMap.contains(-1);
66   auto C2 = MyMap.count(-2) == 0;
67   // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: use 'contains' to check for membership [readability-container-contains]
68   // CHECK-FIXES: auto C2 = !MyMap.contains(-2);
69   auto C3 = MyMap.count(-3) <= 0;
70   // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: use 'contains' to check for membership [readability-container-contains]
71   // CHECK-FIXES: auto C3 = !MyMap.contains(-3);
72   auto C4 = MyMap.count(-4) < 1;
73   // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: use 'contains' to check for membership [readability-container-contains]
74   // CHECK-FIXES: auto C4 = !MyMap.contains(-4);
75   auto C5 = MyMap.find(-5) == MyMap.end();
76   // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: use 'contains' to check for membership [readability-container-contains]
77   // CHECK-FIXES: auto C5 = !MyMap.contains(-5);
78   return C1 + C2 + C3 + C4 + C5;
79 }
80 
81 // Check for various types
82 int testDifferentTypes(std::map<int, int> &M, std::unordered_set<int> &US, std::set<int> &S, std::multimap<int, int> &MM) {
83   bool C1 = M.count(1001);
84   // CHECK-MESSAGES: :[[@LINE-1]]:15: warning: use 'contains' to check for membership [readability-container-contains]
85   // CHECK-FIXES: bool C1 = M.contains(1001);
86   bool C2 = US.count(1002);
87   // CHECK-MESSAGES: :[[@LINE-1]]:16: warning: use 'contains' to check for membership [readability-container-contains]
88   // CHECK-FIXES: bool C2 = US.contains(1002);
89   bool C3 = S.count(1003);
90   // CHECK-MESSAGES: :[[@LINE-1]]:15: warning: use 'contains' to check for membership [readability-container-contains]
91   // CHECK-FIXES: bool C3 = S.contains(1003);
92   bool C4 = MM.count(1004);
93   // CHECK-MESSAGES: :[[@LINE-1]]:16: warning: use 'contains' to check for membership [readability-container-contains]
94   // CHECK-FIXES: bool C4 = MM.contains(1004);
95   return C1 + C2 + C3 + C4;
96 }
97 
98 // The check detects all kind of `const`, reference, rvalue-reference and value types.
99 int testQualifiedTypes(std::map<int, int> ValueM, std::map<int, int> &RefM, const std::map<int, int> &ConstRefM, std::map<int, int> &&RValueM) {
100   bool C1 = ValueM.count(2001);
101   // CHECK-MESSAGES: :[[@LINE-1]]:20: warning: use 'contains' to check for membership [readability-container-contains]
102   // CHECK-FIXES: bool C1 = ValueM.contains(2001);
103   bool C2 = RefM.count(2002);
104   // CHECK-MESSAGES: :[[@LINE-1]]:18: warning: use 'contains' to check for membership [readability-container-contains]
105   // CHECK-FIXES: bool C2 = RefM.contains(2002);
106   bool C3 = ConstRefM.count(2003);
107   // CHECK-MESSAGES: :[[@LINE-1]]:23: warning: use 'contains' to check for membership [readability-container-contains]
108   // CHECK-FIXES: bool C3 = ConstRefM.contains(2003);
109   bool C4 = RValueM.count(2004);
110   // CHECK-MESSAGES: :[[@LINE-1]]:21: warning: use 'contains' to check for membership [readability-container-contains]
111   // CHECK-FIXES: bool C4 = RValueM.contains(2004);
112   return C1 + C2 + C3 + C4;
113 }
114 
115 // This is effectively a membership check, as the result is implicitly casted
116 // to `bool`.
117 bool returnContains(std::map<int, int> &M) {
118   return M.count(42);
119   // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: use 'contains' to check for membership [readability-container-contains]
120   // CHECK-FIXES: return M.contains(42);
121 }
122 
123 // This returns the actual count and should not be rewritten
124 int actualCount(std::multimap<int, int> &M) {
125   return M.count(21);
126   // NO-WARNING.
127   // CHECK-FIXES: return M.count(21);
128 }
129 
130 // Check that we are not confused by aliases
131 namespace s2 = std;
132 using MyMapT = s2::map<int, int>;
133 int typeAliases(MyMapT &MyMap) {
134   bool C1 = MyMap.count(99);
135   // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: use 'contains' to check for membership [readability-container-contains]
136   // CHECK-FIXES: bool C1 = MyMap.contains(99);
137   return C1;
138 }
139 
140 // Check that the tests also trigger for a local variable and not only for
141 // function arguments.
142 bool localVar() {
143   using namespace std;
144   map<int, int> LocalM;
145   return LocalM.count(42);
146   // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: use 'contains' to check for membership [readability-container-contains]
147   // CHECK-FIXES: return LocalM.contains(42);
148 }
149 
150 // Check various usages of an actual `count` which isn't rewritten
151 int nonRewrittenCount(std::multimap<int, int> &MyMap) {
152   // This is an actual test if we have at least 2 usages. Shouldn't be rewritten.
153   bool C1 = MyMap.count(1) >= 2;
154   // NO-WARNING.
155   // CHECK-FIXES: bool C1 = MyMap.count(1) >= 2;
156 
157   // "< 0" makes little sense and is always `false`. Still, let's ensure we
158   // don't accidentally rewrite it to 'contains'.
159   bool C2 = MyMap.count(2) < 0;
160   // NO-WARNING.
161   // CHECK-FIXES: bool C2 = MyMap.count(2) < 0;
162 
163   // The `count` is used in some more complicated formula.
164   bool C3 = MyMap.count(1) + MyMap.count(2) * 2 + MyMap.count(3) / 3 >= 20;
165   // NO-WARNING.
166   // CHECK-FIXES: bool C3 = MyMap.count(1) + MyMap.count(2) * 2 + MyMap.count(3) / 3 >= 20;
167 
168   // This could theoretically be rewritten into a 'contains' after removig the
169   // `4` on both sides of the comparison. For the time being, we don't detect
170   // this case.
171   bool C4 = MyMap.count(1) + 4 > 4;
172   // NO-WARNING.
173   // CHECK-FIXES: bool C4 = MyMap.count(1) + 4 > 4;
174 
175   return C1 + C2 + C3 + C4;
176 }
177 
178 // Check different integer literal suffixes
179 int testDifferentIntegerLiteralSuffixes(std::map<int, int> &MyMap) {
180 
181   auto C1 = MyMap.count(2) != 0U;
182   // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: use 'contains' to check for membership [readability-container-contains]
183   // CHECK-FIXES: auto C1 = MyMap.contains(2);
184   auto C2 = MyMap.count(2) != 0UL;
185   // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: use 'contains' to check for membership [readability-container-contains]
186   // CHECK-FIXES: auto C2 = MyMap.contains(2);
187   auto C3 = 0U != MyMap.count(2);
188   // CHECK-MESSAGES: :[[@LINE-1]]:25: warning: use 'contains' to check for membership [readability-container-contains]
189   // CHECK-FIXES: auto C3 = MyMap.contains(2);
190   auto C4 = 0UL != MyMap.count(2);
191   // CHECK-MESSAGES: :[[@LINE-1]]:26: warning: use 'contains' to check for membership [readability-container-contains]
192   // CHECK-FIXES: auto C4 = MyMap.contains(2);
193   auto C5 = MyMap.count(2) < 1U;
194   // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: use 'contains' to check for membership [readability-container-contains]
195   // CHECK-FIXES: auto C5 = !MyMap.contains(2);
196   auto C6 = MyMap.count(2) < 1UL;
197   // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: use 'contains' to check for membership [readability-container-contains]
198   // CHECK-FIXES: auto C6 = !MyMap.contains(2);
199   auto C7 = 1U > MyMap.count(2);
200   // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: use 'contains' to check for membership [readability-container-contains]
201   // CHECK-FIXES: auto C7 = !MyMap.contains(2);
202   auto C8 = 1UL > MyMap.count(2);
203   // CHECK-MESSAGES: :[[@LINE-1]]:25: warning: use 'contains' to check for membership [readability-container-contains]
204   // CHECK-FIXES: auto C8 = !MyMap.contains(2);
205 
206   return C1 + C2 + C3 + C4 + C5 + C6 + C7 + C8;
207 }
208 
209 // We don't want to rewrite if the `contains` call is from a macro expansion
210 int testMacroExpansion(std::unordered_set<int> &MySet) {
211 #define COUNT_ONES(SET) SET.count(1)
212   // Rewriting the macro would break the code
213   // CHECK-FIXES: #define COUNT_ONES(SET) SET.count(1)
214   // We still want to warn the user even if we don't offer a fixit
215   if (COUNT_ONES(MySet)) {
216     // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use 'contains' to check for membership [readability-container-contains]
217     // CHECK-MESSAGES: note: expanded from macro 'COUNT_ONES'
218     return COUNT_ONES(MySet);
219   }
220 #undef COUNT_ONES
221 #define COUNT_ONES count(1)
222   // Rewriting the macro would break the code
223   // CHECK-FIXES: #define COUNT_ONES count(1)
224   // We still want to warn the user even if we don't offer a fixit
225   if (MySet.COUNT_ONES) {
226     // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: use 'contains' to check for membership [readability-container-contains]
227     // CHECK-MESSAGES: note: expanded from macro 'COUNT_ONES'
228     return MySet.COUNT_ONES;
229   }
230 #undef COUNT_ONES
231 #define MY_SET MySet
232   // CHECK-FIXES: #define MY_SET MySet
233   // We still want to rewrite one of the two calls to `count`
234   if (MY_SET.count(1)) {
235     // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: use 'contains' to check for membership [readability-container-contains]
236     // CHECK-FIXES: if (MY_SET.contains(1)) {
237     return MY_SET.count(1);
238   }
239 #undef MY_SET
240   return 0;
241 }
242 
243 // The following map has the same interface as `std::map`.
244 template <class Key, class T>
245 struct CustomMap {
246   unsigned count(const Key &K) const;
247   bool contains(const Key &K) const;
248   void *find(const Key &K);
249   void *end();
250 };
251 
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)) {};
278 }
279 
280 template <class Key, class T>
281 struct CustomMapContainsDeleted {
282   unsigned count(const Key &K) const;
283   bool contains(const Key &K) const = delete;
284   void *find(const Key &K);
285   void *end();
286 };
287 
288 struct SubmapContainsDeleted : public CustomMapContainsDeleted<int, int> {};
289 
290 void testContainsDeleted(CustomMapContainsDeleted<int, int> &MyMap,
291                          SubmapContainsDeleted &MyMap2) {
292   // No warning if the `contains` method is deleted.
293   if (MyMap.count(0)) {};
294   if (MyMap2.count(0)) {};
295 }
296 
297 template <class Key, class T>
298 struct CustomMapPrivateContains {
299   unsigned count(const Key &K) const;
300   void *find(const Key &K);
301   void *end();
302 
303 private:
304   bool contains(const Key &K) const;
305 };
306 
307 struct SubmapPrivateContains : public CustomMapPrivateContains<int, int> {};
308 
309 void testPrivateContains(CustomMapPrivateContains<int, int> &MyMap,
310                          SubmapPrivateContains &MyMap2) {
311   // No warning if the `contains` method is not public.
312   if (MyMap.count(0)) {};
313   if (MyMap2.count(0)) {};
314 }
315 
316 struct MyString {};
317 
318 struct WeirdNonMatchingContains {
319   unsigned count(char) const;
320   bool contains(const MyString&) const;
321 };
322 
323 void testWeirdNonMatchingContains(WeirdNonMatchingContains &MyMap) {
324   // No warning if there is no `contains` method with the right type.
325   if (MyMap.count('a')) {};
326 }
327 
328 template <class T>
329 struct SmallPtrSet {
330   using ConstPtrType = const T*;
331   unsigned count(ConstPtrType Ptr) const;
332   bool contains(ConstPtrType Ptr) const;
333 };
334 
335 template <class T>
336 struct SmallPtrPtrSet {
337   using ConstPtrType = const T**;
338   unsigned count(ConstPtrType Ptr) const;
339   bool contains(ConstPtrType Ptr) const;
340 };
341 
342 template <class T>
343 struct SmallPtrPtrPtrSet {
344   using ConstPtrType = const T***;
345   unsigned count(ConstPtrType Ptr) const;
346   bool contains(ConstPtrType Ptr) const;
347 };
348 
349 void testSmallPtrSet(const int ***Ptr,
350                      SmallPtrSet<int> &MySet,
351                      SmallPtrPtrSet<int> &MySet2,
352                      SmallPtrPtrPtrSet<int> &MySet3) {
353   if (MySet.count(**Ptr)) {};
354   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use 'contains' to check for membership [readability-container-contains]
355   // CHECK-FIXES: if (MySet.contains(**Ptr)) {};
356   if (MySet2.count(*Ptr)) {};
357   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use 'contains' to check for membership [readability-container-contains]
358   // CHECK-FIXES: if (MySet2.contains(*Ptr)) {};
359   if (MySet3.count(Ptr)) {};
360   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use 'contains' to check for membership [readability-container-contains]
361   // CHECK-FIXES: if (MySet3.contains(Ptr)) {};
362 }
363 
364 struct X {};
365 struct Y : public X {};
366 
367 void testSubclassEntry(SmallPtrSet<X>& Set, Y* Entry) {
368   if (Set.count(Entry)) {}
369   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use 'contains' to check for membership [readability-container-contains]
370   // CHECK-FIXES: if (Set.contains(Entry)) {}
371 }
372 
373 struct WeirdPointerApi {
374   unsigned count(int** Ptr) const;
375   bool contains(int* Ptr) const;
376 };
377 
378 void testWeirdApi(WeirdPointerApi& Set, int* E) {
379   if (Set.count(&E)) {}
380 }
381 
382 void testIntUnsigned(std::set<int>& S, unsigned U) {
383   if (S.count(U)) {}
384   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use 'contains' to check for membership [readability-container-contains]
385   // CHECK-FIXES: if (S.contains(U)) {}
386 }
387 
388 template <class T>
389 struct CustomSetConvertible {
390   unsigned count(const T &K) const;
391   bool contains(const T &K) const;
392 };
393 
394 struct A {};
395 struct B { B() = default; B(const A&) {} };
396 struct C { operator A() const; };
397 
398 void testConvertibleTypes() {
399   CustomSetConvertible<B> MyMap;
400   if (MyMap.count(A())) {};
401   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use 'contains' to check for membership [readability-container-contains]
402   // CHECK-FIXES: if (MyMap.contains(A())) {};
403 
404   CustomSetConvertible<A> MyMap2;
405   if (MyMap2.count(C())) {};
406   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use 'contains' to check for membership [readability-container-contains]
407   // CHECK-FIXES: if (MyMap2.contains(C())) {};
408 
409   if (MyMap2.count(C()) != 0) {};
410   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use 'contains' to check for membership [readability-container-contains]
411   // CHECK-FIXES: if (MyMap2.contains(C())) {};
412 }
413 
414 template<class U>
415 using Box = const U& ;
416 
417 template <class T>
418 struct CustomBoxedSet {
419   unsigned count(Box<T> K) const;
420   bool contains(Box<T> K) const;
421 };
422 
423 void testBox() {
424   CustomBoxedSet<int> Set;
425   if (Set.count(0)) {};
426   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use 'contains' to check for membership [readability-container-contains]
427   // CHECK-FIXES: if (Set.contains(0)) {};
428 }
429