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