[clang-tools-extra] [clang-tidy] Extend `performance-faster-string-find` to handle ternary operator arguments (PR #187069)
Victor Chernyakin via cfe-commits
cfe-commits at lists.llvm.org
Fri Mar 20 07:41:24 PDT 2026
https://github.com/localspook updated https://github.com/llvm/llvm-project/pull/187069
>From 9ee95a306065c4a135cca0b1d9c4af15e1b699ff Mon Sep 17 00:00:00 2001
From: Victor Chernyakin <chernyakin.victor.j at outlook.com>
Date: Tue, 17 Mar 2026 17:09:27 +0000
Subject: [PATCH 1/3] [clang-tidy] Extend `performrformance-faster-string-find`
to handle ternary operator arguments
---
.../performance/FasterStringFindCheck.cpp | 57 ++++++++++++-------
clang-tools-extra/docs/ReleaseNotes.rst | 3 +
.../checks/performance/faster-string-find.rst | 2 +
.../performance/faster-string-find.cpp | 16 ++++++
4 files changed, 59 insertions(+), 19 deletions(-)
diff --git a/clang-tools-extra/clang-tidy/performance/FasterStringFindCheck.cpp b/clang-tools-extra/clang-tidy/performance/FasterStringFindCheck.cpp
index 1d9325166e341..43c0bd37dab9a 100644
--- a/clang-tools-extra/clang-tidy/performance/FasterStringFindCheck.cpp
+++ b/clang-tools-extra/clang-tidy/performance/FasterStringFindCheck.cpp
@@ -11,28 +11,24 @@
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "llvm/Support/raw_ostream.h"
-#include <optional>
using namespace clang::ast_matchers;
namespace clang::tidy::performance {
-static std::optional<std::string>
-makeCharacterLiteral(const StringLiteral *Literal) {
+static std::string makeCharacterLiteral(const StringLiteral *Literal) {
std::string Result;
{
llvm::raw_string_ostream OS(Result);
Literal->outputString(OS);
}
// Now replace the " with '.
- auto OpenPos = Result.find_first_of('"');
- if (OpenPos == std::string::npos)
- return std::nullopt;
+ const size_t OpenPos = Result.find_first_of('"');
+ assert(OpenPos != std::string::npos);
Result[OpenPos] = '\'';
- auto ClosePos = Result.find_last_of('"');
- if (ClosePos == std::string::npos)
- return std::nullopt;
+ const size_t ClosePos = Result.find_last_of('"');
+ assert(ClosePos != std::string::npos);
Result[ClosePos] = '\'';
// "'" is OK, but ''' is not, so add a backslash
@@ -42,6 +38,23 @@ makeCharacterLiteral(const StringLiteral *Literal) {
return Result;
}
+template <typename T>
+static bool forAllLeavesOfTernaryTree(const Expr *E, const T &HandleNode) {
+ if constexpr (std::is_void_v<decltype(HandleNode(E))>) {
+ return forAllLeavesOfTernaryTree(E, [&](const Expr *Exp) {
+ HandleNode(Exp);
+ return true;
+ });
+ } else {
+ E = E->IgnoreParenImpCasts();
+ if (const auto *Ternary = dyn_cast<ConditionalOperator>(E)) {
+ return forAllLeavesOfTernaryTree(Ternary->getTrueExpr(), HandleNode) &&
+ forAllLeavesOfTernaryTree(Ternary->getFalseExpr(), HandleNode);
+ }
+ return HandleNode(E);
+ }
+}
+
FasterStringFindCheck::FasterStringFindCheck(StringRef Name,
ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
@@ -55,8 +68,7 @@ void FasterStringFindCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
}
void FasterStringFindCheck::registerMatchers(MatchFinder *Finder) {
- const auto SingleChar =
- ignoringParenCasts(stringLiteral(hasSize(1)).bind("literal"));
+ const auto SingleChar = expr().bind("literal");
const auto StringExpr = expr(hasType(hasUnqualifiedDesugaredType(
recordType(hasDeclaration(recordDecl(hasAnyName(StringLikeClasses)))))));
@@ -79,18 +91,25 @@ void FasterStringFindCheck::registerMatchers(MatchFinder *Finder) {
}
void FasterStringFindCheck::check(const MatchFinder::MatchResult &Result) {
- const auto *Literal = Result.Nodes.getNodeAs<StringLiteral>("literal");
const auto *FindFunc = Result.Nodes.getNodeAs<FunctionDecl>("func");
+ const auto *Literal = Result.Nodes.getNodeAs<Expr>("literal");
- auto Replacement = makeCharacterLiteral(Literal);
- if (!Replacement)
+ if (!forAllLeavesOfTernaryTree(Literal, [](const Expr *E) {
+ const auto *Literal = dyn_cast<StringLiteral>(E);
+ return Literal && Literal->getLength() == 1;
+ }))
return;
- diag(Literal->getBeginLoc(), "%0 called with a string literal consisting of "
- "a single character; consider using the more "
- "effective overload accepting a character")
- << FindFunc
- << FixItHint::CreateReplacement(Literal->getSourceRange(), *Replacement);
+ const auto Diag = diag(Literal->getBeginLoc(),
+ "%0 called with a string literal consisting of "
+ "a single character; consider using the more "
+ "effective overload accepting a character")
+ << FindFunc;
+
+ forAllLeavesOfTernaryTree(Literal, [&](const Expr *E) {
+ Diag << FixItHint::CreateReplacement(
+ E->getSourceRange(), makeCharacterLiteral(cast<StringLiteral>(E)));
+ });
}
} // namespace clang::tidy::performance
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index c9a170a9e8660..61ef1585d6f2f 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -319,6 +319,9 @@ Changes in existing checks
- Now analyzes calls to the ``starts_with``, ``ends_with``, ``contains``,
and ``operator+=`` string member functions.
+ - Now analyzes cases where the argument to a string function is a ternary
+ operator, all the branches of which are single-character strings.
+
- Fixes false negatives when using ``std::set`` from ``libstdc++``.
- Improved :doc:`performance-inefficient-string-concatenation
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 e7ed869acc8ad..4cfc9fa0b651d 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
@@ -12,10 +12,12 @@ Examples:
.. code-block:: c++
str.find("A");
+ str.contains(foo ? "0" : "1");
// becomes
str.find('A');
+ str.contains(foo ? '0' : '1');
Options
-------
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 3999049a10718..5bc490b4ab0c3 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
@@ -46,6 +46,22 @@ void StringFind() {
// CHECK-MESSAGES: [[@LINE-1]]:12: warning: 'find' called with a string literal
// CHECK-FIXES: Str.find('\'');
+ // Works with arbitrarily complex ternary operators.
+ Str.find(true ? "a" : "b");
+ // CHECK-MESSAGES: [[@LINE-1]]:12: warning: 'find' called with a string literal consisting of a single character; consider using the more effective overload accepting a character [performance-faster-string-find]
+ // CHECK-FIXES: Str.find(true ? 'a' : 'b');
+
+ Str.find(true ? (false ? "a" : "b") : "c");
+ // CHECK-MESSAGES: [[@LINE-1]]:12: warning: 'find' called with a string literal consisting of a single character; consider using the more effective overload accepting a character [performance-faster-string-find]
+ // CHECK-FIXES: Str.find(true ? (false ? 'a' : 'b') : 'c');
+
+ Str.find(true ? "a" : true ? "b" : true ? "c" : "d");
+ // CHECK-MESSAGES: [[@LINE-1]]:12: warning: 'find' called with a string literal consisting of a single character; consider using the more effective overload accepting a character [performance-faster-string-find]
+ // CHECK-FIXES: Str.find(true ? 'a' : true ? 'b' : true ? 'c' : 'd');
+
+ Str.find(true ? "a" : true ? "b" : true ? "c" : "not one character");
+ Str.find("x" ?: "y");
+
// Other methods that can also be replaced
Str.rfind("a");
// CHECK-MESSAGES: [[@LINE-1]]:13: warning: 'rfind' called with a string literal
>From 72be3e1b0f1f6c51a683217463a05eadb24278e4 Mon Sep 17 00:00:00 2001
From: Victor Chernyakin <chernyakin.victor.j at outlook.com>
Date: Fri, 20 Mar 2026 09:38:04 -0500
Subject: [PATCH 2/3] NFC: simplify code, rename some variables
---
.../performance/FasterStringFindCheck.cpp | 20 +++++++------------
1 file changed, 7 insertions(+), 13 deletions(-)
diff --git a/clang-tools-extra/clang-tidy/performance/FasterStringFindCheck.cpp b/clang-tools-extra/clang-tidy/performance/FasterStringFindCheck.cpp
index 43c0bd37dab9a..9858c32d7fe4f 100644
--- a/clang-tools-extra/clang-tidy/performance/FasterStringFindCheck.cpp
+++ b/clang-tools-extra/clang-tidy/performance/FasterStringFindCheck.cpp
@@ -39,20 +39,13 @@ static std::string makeCharacterLiteral(const StringLiteral *Literal) {
}
template <typename T>
-static bool forAllLeavesOfTernaryTree(const Expr *E, const T &HandleNode) {
- if constexpr (std::is_void_v<decltype(HandleNode(E))>) {
- return forAllLeavesOfTernaryTree(E, [&](const Expr *Exp) {
- HandleNode(Exp);
- return true;
- });
- } else {
- E = E->IgnoreParenImpCasts();
- if (const auto *Ternary = dyn_cast<ConditionalOperator>(E)) {
- return forAllLeavesOfTernaryTree(Ternary->getTrueExpr(), HandleNode) &&
- forAllLeavesOfTernaryTree(Ternary->getFalseExpr(), HandleNode);
- }
- return HandleNode(E);
+static bool forAllLeavesOfTernaryTree(const Expr *Node, const T &HandleLeaf) {
+ Node = Node->IgnoreParenImpCasts();
+ if (const auto *Ternary = dyn_cast<ConditionalOperator>(Node)) {
+ return forAllLeavesOfTernaryTree(Ternary->getTrueExpr(), HandleLeaf) &&
+ forAllLeavesOfTernaryTree(Ternary->getFalseExpr(), HandleLeaf);
}
+ return HandleLeaf(Node);
}
FasterStringFindCheck::FasterStringFindCheck(StringRef Name,
@@ -109,6 +102,7 @@ void FasterStringFindCheck::check(const MatchFinder::MatchResult &Result) {
forAllLeavesOfTernaryTree(Literal, [&](const Expr *E) {
Diag << FixItHint::CreateReplacement(
E->getSourceRange(), makeCharacterLiteral(cast<StringLiteral>(E)));
+ return true;
});
}
>From 6172d9ed0a017cb57867f839bf874102b6f2c112 Mon Sep 17 00:00:00 2001
From: Victor Chernyakin <chernyakin.victor.j at outlook.com>
Date: Fri, 20 Mar 2026 09:40:37 -0500
Subject: [PATCH 3/3] Update warning message
---
.../clang-tidy/performance/FasterStringFindCheck.cpp | 2 +-
.../clang-tidy/checkers/performance/faster-string-find.cpp | 6 +++---
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/clang-tools-extra/clang-tidy/performance/FasterStringFindCheck.cpp b/clang-tools-extra/clang-tidy/performance/FasterStringFindCheck.cpp
index 9858c32d7fe4f..b186a3c26f31f 100644
--- a/clang-tools-extra/clang-tidy/performance/FasterStringFindCheck.cpp
+++ b/clang-tools-extra/clang-tidy/performance/FasterStringFindCheck.cpp
@@ -96,7 +96,7 @@ void FasterStringFindCheck::check(const MatchFinder::MatchResult &Result) {
const auto Diag = diag(Literal->getBeginLoc(),
"%0 called with a string literal consisting of "
"a single character; consider using the more "
- "effective overload accepting a character")
+ "efficient overload accepting a character")
<< FindFunc;
forAllLeavesOfTernaryTree(Literal, [&](const Expr *E) {
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 5bc490b4ab0c3..0ddda89ca4337 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
@@ -48,15 +48,15 @@ void StringFind() {
// Works with arbitrarily complex ternary operators.
Str.find(true ? "a" : "b");
- // CHECK-MESSAGES: [[@LINE-1]]:12: warning: 'find' called with a string literal consisting of a single character; consider using the more effective overload accepting a character [performance-faster-string-find]
+ // CHECK-MESSAGES: [[@LINE-1]]:12: warning: 'find' called with a string literal
// CHECK-FIXES: Str.find(true ? 'a' : 'b');
Str.find(true ? (false ? "a" : "b") : "c");
- // CHECK-MESSAGES: [[@LINE-1]]:12: warning: 'find' called with a string literal consisting of a single character; consider using the more effective overload accepting a character [performance-faster-string-find]
+ // CHECK-MESSAGES: [[@LINE-1]]:12: warning: 'find' called with a string literal
// CHECK-FIXES: Str.find(true ? (false ? 'a' : 'b') : 'c');
Str.find(true ? "a" : true ? "b" : true ? "c" : "d");
- // CHECK-MESSAGES: [[@LINE-1]]:12: warning: 'find' called with a string literal consisting of a single character; consider using the more effective overload accepting a character [performance-faster-string-find]
+ // CHECK-MESSAGES: [[@LINE-1]]:12: warning: 'find' called with a string literal
// CHECK-FIXES: Str.find(true ? 'a' : true ? 'b' : true ? 'c' : 'd');
Str.find(true ? "a" : true ? "b" : true ? "c" : "not one character");
More information about the cfe-commits
mailing list