Skip to content

Commit 2f02b5a

Browse files
authored
[clang-tidy][modernize-use-starts-ends-with] Fix operator rewriting false negative (#117837)
In C++20, `operator!=` can be rewritten by negating `operator==`. This is the case for `std::string`, where `operator!=` is not provided hence relying on this rewriting. Cover this case by matching `binaryOperation` and adding one case to `isNegativeComparison`.
1 parent b717044 commit 2f02b5a

File tree

3 files changed

+23
-7
lines changed

3 files changed

+23
-7
lines changed

clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,16 @@ using namespace clang::ast_matchers;
2020
namespace clang::tidy::modernize {
2121

2222
static bool isNegativeComparison(const Expr *ComparisonExpr) {
23-
if (const auto *BO = llvm::dyn_cast<BinaryOperator>(ComparisonExpr))
24-
return BO->getOpcode() == BO_NE;
23+
if (const auto *Op = llvm::dyn_cast<BinaryOperator>(ComparisonExpr))
24+
return Op->getOpcode() == BO_NE;
2525

2626
if (const auto *Op = llvm::dyn_cast<CXXOperatorCallExpr>(ComparisonExpr))
2727
return Op->getOperator() == OO_ExclaimEqual;
2828

29+
if (const auto *Op =
30+
llvm::dyn_cast<CXXRewrittenBinaryOperator>(ComparisonExpr))
31+
return Op->getOperator() == BO_NE;
32+
2933
return false;
3034
}
3135

@@ -185,7 +189,7 @@ void UseStartsEndsWithCheck::registerMatchers(MatchFinder *Finder) {
185189

186190
// Case 6: X.substr(0, LEN(Y)) [!=]= Y -> starts_with.
187191
Finder->addMatcher(
188-
cxxOperatorCallExpr(
192+
binaryOperation(
189193
hasAnyOperatorName("==", "!="),
190194
hasOperands(
191195
expr().bind("needle"),

clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/string

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,10 +136,6 @@ bool operator==(const std::string&, const std::string&);
136136
bool operator==(const std::string&, const char*);
137137
bool operator==(const char*, const std::string&);
138138

139-
bool operator!=(const std::string&, const std::string&);
140-
bool operator!=(const std::string&, const char*);
141-
bool operator!=(const char*, const std::string&);
142-
143139
bool operator==(const std::wstring&, const std::wstring&);
144140
bool operator==(const std::wstring&, const wchar_t*);
145141
bool operator==(const wchar_t*, const std::wstring&);
@@ -148,9 +144,15 @@ bool operator==(const std::string_view&, const std::string_view&);
148144
bool operator==(const std::string_view&, const char*);
149145
bool operator==(const char*, const std::string_view&);
150146

147+
#if __cplusplus < 202002L
148+
bool operator!=(const std::string&, const std::string&);
149+
bool operator!=(const std::string&, const char*);
150+
bool operator!=(const char*, const std::string&);
151+
151152
bool operator!=(const std::string_view&, const std::string_view&);
152153
bool operator!=(const std::string_view&, const char*);
153154
bool operator!=(const char*, const std::string_view&);
155+
#endif
154156

155157
size_t strlen(const char* str);
156158
}

clang-tools-extra/test/clang-tidy/checkers/modernize/use-starts-ends-with.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,3 +320,13 @@ void test_substr() {
320320

321321
str.substr(0, strlen("hello123")) == "hello";
322322
}
323+
324+
void test_operator_rewriting(std::string str, std::string prefix) {
325+
str.substr(0, prefix.size()) == prefix;
326+
// CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of substr
327+
// CHECK-FIXES: str.starts_with(prefix);
328+
329+
str.substr(0, prefix.size()) != prefix;
330+
// CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with instead of substr
331+
// CHECK-FIXES: !str.starts_with(prefix);
332+
}

0 commit comments

Comments
 (0)