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