[clang-tools-extra] [clang-tidy] Teach `performance-faster-string-find` about `starts_with`, `ends_with`, and `contains` (PR #182633)
Victor Chernyakin via cfe-commits
cfe-commits at lists.llvm.org
Fri Feb 20 21:03:08 PST 2026
https://github.com/localspook updated https://github.com/llvm/llvm-project/pull/182633
>From be680e5dee3af09eeca33577a312dab4be8892bd Mon Sep 17 00:00:00 2001
From: Victor Chernyakin <chernyakin.victor.j at outlook.com>
Date: Fri, 20 Feb 2026 16:29:03 -0800
Subject: [PATCH 1/2] [clang-tidy] Teach `performance-faster-string-find` about
`starts_with`, `ends_with`, and `contains`
---
.../performance/FasterStringFindCheck.cpp | 14 ++++++--------
clang-tools-extra/docs/ReleaseNotes.rst | 5 +++++
.../checks/performance/faster-string-find.rst | 7 ++++---
.../checkers/performance/faster-string-find.cpp | 15 +++++++++++++++
4 files changed, 30 insertions(+), 11 deletions(-)
diff --git a/clang-tools-extra/clang-tidy/performance/FasterStringFindCheck.cpp b/clang-tools-extra/clang-tidy/performance/FasterStringFindCheck.cpp
index 7c90130c826f0..52a4d70e15265 100644
--- a/clang-tools-extra/clang-tidy/performance/FasterStringFindCheck.cpp
+++ b/clang-tools-extra/clang-tidy/performance/FasterStringFindCheck.cpp
@@ -67,13 +67,14 @@ void FasterStringFindCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
void FasterStringFindCheck::registerMatchers(MatchFinder *Finder) {
const auto SingleChar =
expr(ignoringParenCasts(stringLiteral(hasSize(1)).bind("literal")));
- const auto StringFindFunctions =
- hasAnyName("find", "rfind", "find_first_of", "find_first_not_of",
- "find_last_of", "find_last_not_of");
+
+ const auto InterestingStringFunction = hasAnyName(
+ "find", "rfind", "find_first_of", "find_first_not_of", "find_last_of",
+ "find_last_not_of", "starts_with", "ends_with", "contains");
Finder->addMatcher(
cxxMemberCallExpr(
- callee(functionDecl(StringFindFunctions).bind("func")),
+ callee(functionDecl(InterestingStringFunction).bind("func")),
anyOf(argumentCountIs(1), argumentCountIs(2)),
hasArgument(0, SingleChar),
on(expr(hasType(hasUnqualifiedDesugaredType(recordType(hasDeclaration(
@@ -94,10 +95,7 @@ void FasterStringFindCheck::check(const MatchFinder::MatchResult &Result) {
"a single character; consider using the more "
"effective overload accepting a character")
<< FindFunc
- << FixItHint::CreateReplacement(
- CharSourceRange::getTokenRange(Literal->getBeginLoc(),
- Literal->getEndLoc()),
- *Replacement);
+ << FixItHint::CreateReplacement(Literal->getSourceRange(), *Replacement);
}
} // namespace clang::tidy::performance
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 68bab88146241..b0a49b139e65e 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -224,6 +224,11 @@ Changes in existing checks
- Improved the ignore list to correctly handle ``typedef`` and ``enum``.
+- Improved :doc:`performance-faster-string-find
+ <clang-tidy/checks/performance/faster-string-find>` check to
+ analyze calls to the ``starts_with``, ``ends_with``, and ``contains``
+ string member functions.
+
- Improved :doc:`performance-inefficient-vector-operation
<clang-tidy/checks/performance/inefficient-vector-operation>` check by
correctly handling vector-like classes when ``push_back``/``emplace_back`` are
diff --git a/clang-tools-extra/docs/clang-tidy/checks/performance/faster-string-find.rst b/clang-tools-extra/docs/clang-tidy/checks/performance/faster-string-find.rst
index 28bd08d8aaa11..a7fb97197460e 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/performance/faster-string-find.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/performance/faster-string-find.rst
@@ -24,6 +24,7 @@ Options
Semicolon-separated list of names of string-like classes. By default only
``::std::basic_string`` and ``::std::basic_string_view`` are considered.
- The check will only consider member functions named ``find``, ``rfind``,
- ``find_first_of``, ``find_first_not_of``, ``find_last_of``, or
- ``find_last_not_of`` within these classes.
+ Within these classes, the check will only consider member functions named
+ ``find``, ``rfind``, ``find_first_of``, ``find_first_not_of``,
+ ``find_last_of``, ``find_last_not_of``, ``starts_with``, ``ends_with``, or
+ ``contains``.
diff --git a/clang-tools-extra/test/clang-tidy/checkers/performance/faster-string-find.cpp b/clang-tools-extra/test/clang-tidy/checkers/performance/faster-string-find.cpp
index e52441035e15f..83824c62494e7 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/performance/faster-string-find.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/performance/faster-string-find.cpp
@@ -14,6 +14,9 @@ struct basic_string {
int find_first_not_of(const Char *) const;
int find_last_of(const Char *) const;
int find_last_not_of(const Char *) const;
+ bool starts_with(const Char *) const;
+ bool ends_with(const Char *) const;
+ bool contains(const Char *) const;
};
typedef basic_string<char> string;
@@ -28,6 +31,9 @@ struct basic_string_view {
int find_first_not_of(const Char *) const;
int find_last_of(const Char *) const;
int find_last_not_of(const Char *) const;
+ bool starts_with(const Char *) const;
+ bool ends_with(const Char *) const;
+ bool contains(const Char *) const;
};
typedef basic_string_view<char> string_view;
@@ -87,6 +93,15 @@ void StringFind() {
Str.find_last_not_of("a");
// CHECK-MESSAGES: [[@LINE-1]]:24: warning: 'find_last_not_of' called with a
// CHECK-FIXES: Str.find_last_not_of('a');
+ Str.starts_with("a");
+ // CHECK-MESSAGES: [[@LINE-1]]:19: warning: 'starts_with' called with a
+ // CHECK-FIXES: Str.starts_with('a');
+ Str.ends_with("a");
+ // CHECK-MESSAGES: [[@LINE-1]]:17: warning: 'ends_with' called with a string
+ // CHECK-FIXES: Str.ends_with('a');
+ Str.contains("a");
+ // CHECK-MESSAGES: [[@LINE-1]]:16: warning: 'contains' called with a
+ // CHECK-FIXES: Str.contains('a');
// std::wstring should work.
std::wstring WStr;
>From cfd715e652d743316c9c8e9b44c1d56eb9555a2b Mon Sep 17 00:00:00 2001
From: Victor Chernyakin <chernyakin.victor.j at outlook.com>
Date: Fri, 20 Feb 2026 21:02:57 -0800
Subject: [PATCH 2/2] [clang-tidy] Teach `performance-faster-string-find` about
`operator+=`
---
.../performance/FasterStringFindCheck.cpp | 20 +++++++++++++------
clang-tools-extra/docs/ReleaseNotes.rst | 4 ++--
.../checks/performance/faster-string-find.rst | 4 ++--
.../performance/faster-string-find.cpp | 7 +++++++
4 files changed, 25 insertions(+), 10 deletions(-)
diff --git a/clang-tools-extra/clang-tidy/performance/FasterStringFindCheck.cpp b/clang-tools-extra/clang-tidy/performance/FasterStringFindCheck.cpp
index 52a4d70e15265..4cba1d00edbb0 100644
--- a/clang-tools-extra/clang-tidy/performance/FasterStringFindCheck.cpp
+++ b/clang-tools-extra/clang-tidy/performance/FasterStringFindCheck.cpp
@@ -10,6 +10,7 @@
#include "../utils/OptionsUtils.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
#include "llvm/Support/raw_ostream.h"
#include <optional>
@@ -66,21 +67,28 @@ void FasterStringFindCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
void FasterStringFindCheck::registerMatchers(MatchFinder *Finder) {
const auto SingleChar =
- expr(ignoringParenCasts(stringLiteral(hasSize(1)).bind("literal")));
+ ignoringParenCasts(stringLiteral(hasSize(1)).bind("literal"));
+
+ const auto StringExpr =
+ expr(hasType(hasUnqualifiedDesugaredType(recordType(
+ hasDeclaration(recordDecl(hasAnyName(StringLikeClasses)))))),
+ unless(hasSubstitutedType()));
const auto InterestingStringFunction = hasAnyName(
"find", "rfind", "find_first_of", "find_first_not_of", "find_last_of",
- "find_last_not_of", "starts_with", "ends_with", "contains");
+ "find_last_not_of", "starts_with", "ends_with", "contains", "operator+=");
Finder->addMatcher(
cxxMemberCallExpr(
callee(functionDecl(InterestingStringFunction).bind("func")),
anyOf(argumentCountIs(1), argumentCountIs(2)),
- hasArgument(0, SingleChar),
- on(expr(hasType(hasUnqualifiedDesugaredType(recordType(hasDeclaration(
- recordDecl(hasAnyName(StringLikeClasses)))))),
- unless(hasSubstitutedType())))),
+ hasArgument(0, SingleChar), on(StringExpr)),
this);
+
+ Finder->addMatcher(cxxOperatorCallExpr(hasOperatorName("+="),
+ hasLHS(StringExpr), hasRHS(SingleChar),
+ callee(functionDecl().bind("func"))),
+ this);
}
void FasterStringFindCheck::check(const MatchFinder::MatchResult &Result) {
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index b0a49b139e65e..cecb7eb638d0d 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -226,8 +226,8 @@ Changes in existing checks
- Improved :doc:`performance-faster-string-find
<clang-tidy/checks/performance/faster-string-find>` check to
- analyze calls to the ``starts_with``, ``ends_with``, and ``contains``
- string member functions.
+ analyze calls to the ``starts_with``, ``ends_with``, ``contains``,
+ and ``operator+=`` string member functions.
- Improved :doc:`performance-inefficient-vector-operation
<clang-tidy/checks/performance/inefficient-vector-operation>` check by
diff --git a/clang-tools-extra/docs/clang-tidy/checks/performance/faster-string-find.rst b/clang-tools-extra/docs/clang-tidy/checks/performance/faster-string-find.rst
index a7fb97197460e..e7ed869acc8ad 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/performance/faster-string-find.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/performance/faster-string-find.rst
@@ -26,5 +26,5 @@ Options
``::std::basic_string`` and ``::std::basic_string_view`` are considered.
Within these classes, the check will only consider member functions named
``find``, ``rfind``, ``find_first_of``, ``find_first_not_of``,
- ``find_last_of``, ``find_last_not_of``, ``starts_with``, ``ends_with``, or
- ``contains``.
+ ``find_last_of``, ``find_last_not_of``, ``starts_with``, ``ends_with``,
+ ``contains``, or ``operator+=``.
diff --git a/clang-tools-extra/test/clang-tidy/checkers/performance/faster-string-find.cpp b/clang-tools-extra/test/clang-tidy/checkers/performance/faster-string-find.cpp
index 83824c62494e7..437e95053660f 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/performance/faster-string-find.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/performance/faster-string-find.cpp
@@ -17,6 +17,7 @@ struct basic_string {
bool starts_with(const Char *) const;
bool ends_with(const Char *) const;
bool contains(const Char *) const;
+ basic_string& operator+=(const Char *);
};
typedef basic_string<char> string;
@@ -102,6 +103,12 @@ void StringFind() {
Str.contains("a");
// CHECK-MESSAGES: [[@LINE-1]]:16: warning: 'contains' called with a
// CHECK-FIXES: Str.contains('a');
+ Str += "a";
+ // CHECK-MESSAGES: [[@LINE-1]]:10: warning: 'operator+=' called with a
+ // CHECK-FIXES: Str += 'a';
+ Str.operator+=("a");
+ // CHECK-MESSAGES: [[@LINE-1]]:18: warning: 'operator+=' called with a
+ // CHECK-FIXES: Str.operator+=('a');
// std::wstring should work.
std::wstring WStr;
More information about the cfe-commits
mailing list