1// RUN: %check_clang_tidy -std=c++20-or-later %s readability-container-contains %t
2
3// Some *very* simplified versions of `map` etc.
4namespace std {
5
6template <class Key, class T>
7struct 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
14template <class Key>
15struct set {
16 unsigned count(const Key &K) const;
17 bool contains(const Key &K) const;
18};
19
20template <class Key>
21struct unordered_set {
22 unsigned count(const Key &K) const;
23 bool contains(const Key &K) const;
24};
25
26template <class Key, class T>
27struct 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
35int testDifferentCheckTypes(std::map<int, int> &MyMap) {
36 if (MyMap.count(K: 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(K: 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(K: 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(K: 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(K: 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(K: 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(K: 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
62int testNegativeChecks(std::map<int, int> &MyMap) {
63 bool C1 = !MyMap.count(K: -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(K: -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(K: -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(K: -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(K: -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
82int 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(K: 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(K: 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(K: 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(K: 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.
99int 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(K: 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(K: 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(K: 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(K: 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`.
117bool returnContains(std::map<int, int> &M) {
118 return M.count(K: 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
124int actualCount(std::multimap<int, int> &M) {
125 return M.count(K: 21);
126 // NO-WARNING.
127 // CHECK-FIXES: return M.count(21);
128}
129
130// Check that we are not confused by aliases
131namespace s2 = std;
132using MyMapT = s2::map<int, int>;
133int typeAliases(MyMapT &MyMap) {
134 bool C1 = MyMap.count(K: 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.
142bool localVar() {
143 using namespace std;
144 map<int, int> LocalM;
145 return LocalM.count(K: 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
151int 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(K: 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(K: 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(K: 1) + MyMap.count(K: 2) * 2 + MyMap.count(K: 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(K: 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
179int testDifferentIntegerLiteralSuffixes(std::map<int, int> &MyMap) {
180
181 auto C1 = MyMap.count(K: 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(K: 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(K: 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(K: 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(K: 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(K: 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(K: 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(K: 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
210int 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(K: 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(K: 1);
238 }
239#undef MY_SET
240 return 0;
241}
242
243// The following map has the same interface like `std::map`.
244template <class Key, class T>
245struct 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// 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.
255void *testDifferentCheckTypes(CustomMap<int, int> &MyMap) {
256 if (MyMap.count(K: 0))
257 // NO-WARNING.
258 // CHECK-FIXES: if (MyMap.count(0))
259 return nullptr;
260 return MyMap.find(K: 2);
261}
262

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