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(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 |
62 | int 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 |
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(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. |
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(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`. |
117 | bool 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 |
124 | int 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 |
131 | namespace s2 = std; |
132 | using MyMapT = s2::map<int, int>; |
133 | int 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. |
142 | bool 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 |
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(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 |
179 | int 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 |
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(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`. |
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 | // 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(K: 0)) |
257 | // NO-WARNING. |
258 | // CHECK-FIXES: if (MyMap.count(0)) |
259 | return nullptr; |
260 | return MyMap.find(K: 2); |
261 | } |
262 | |