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