xref: /llvm-project/clang-tools-extra/test/clang-tidy/checkers/readability/container-contains.cpp (revision 89a1d03e2b379e325daa5249411e414bbd995b5e)
1 // RUN: %check_clang_tidy -std=c++20 %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 // We don't want to rewrite if the `contains` call is from a macro expansion
179 int testMacroExpansion(std::unordered_set<int> &MySet) {
180 #define COUNT_ONES(SET) SET.count(1)
181   // Rewriting the macro would break the code
182   // CHECK-FIXES: #define COUNT_ONES(SET) SET.count(1)
183   // We still want to warn the user even if we don't offer a fixit
184   if (COUNT_ONES(MySet)) {
185     // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use 'contains' to check for membership [readability-container-contains]
186     // CHECK-MESSAGES: note: expanded from macro 'COUNT_ONES'
187     return COUNT_ONES(MySet);
188   }
189 #undef COUNT_ONES
190 #define COUNT_ONES count(1)
191   // Rewriting the macro would break the code
192   // CHECK-FIXES: #define COUNT_ONES count(1)
193   // We still want to warn the user even if we don't offer a fixit
194   if (MySet.COUNT_ONES) {
195     // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: use 'contains' to check for membership [readability-container-contains]
196     // CHECK-MESSAGES: note: expanded from macro 'COUNT_ONES'
197     return MySet.COUNT_ONES;
198   }
199 #undef COUNT_ONES
200 #define MY_SET MySet
201   // CHECK-FIXES: #define MY_SET MySet
202   // We still want to rewrite one of the two calls to `count`
203   if (MY_SET.count(1)) {
204     // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: use 'contains' to check for membership [readability-container-contains]
205     // CHECK-FIXES: if (MY_SET.contains(1)) {
206     return MY_SET.count(1);
207   }
208 #undef MY_SET
209   return 0;
210 }
211 
212 // The following map has the same interface like `std::map`.
213 template <class Key, class T>
214 struct CustomMap {
215   unsigned count(const Key &K) const;
216   bool contains(const Key &K) const;
217   void *find(const Key &K);
218   void *end();
219 };
220 
221 // The clang-tidy check is currently hard-coded against the `std::` containers
222 // and hence won't annotate the following instance. We might change this in the
223 // future and also detect the following case.
224 void *testDifferentCheckTypes(CustomMap<int, int> &MyMap) {
225   if (MyMap.count(0))
226     // NO-WARNING.
227     // CHECK-FIXES: if (MyMap.count(0))
228     return nullptr;
229   return MyMap.find(2);
230 }
231