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